class SystemRulesView(BaseLoggedInPage):
    """This view represents System rules tab page"""

    paginator = Pagination(
        locator='.//div[contains(@class, "pf-c-pagination")]')

    show_all_rules = Text(".//input[@id='showAllRules']")
    table = Table(
        locator='.//table[contains(@aria-label, "Table")]',
        column_widgets={
            "Provider ID":
            Button(locator='.//th[@data-label="Provider ID"]/button'),
            "Number of rules":
            Button(locator=".//th[@data-label='Number of rules']/button"),
            4:
            Dropdown(),
        },
    )
    filter_type_selector = DropdownMenu(
        locator='.//div[contains(@class, "pf-c-select")]')
    filter_by = MTACheckboxSelect(
        locator='.//span[@class="pf-c-select__toggle-arrow"]//parent::button['
        'contains(@aria-labelledby, "Filter by Source")]/parent::div |'
        ' .//span[@class="pf-c-select__toggle-arrow"]//parent::button['
        'contains(@aria-labelledby, "Filter by Target")]/parent::div')

    clear = Text('.//button[contains(text(), "Clear all filters")]')

    @property
    def is_displayed(self):
        return self.table.is_displayed and self.show_all_rules.is_displayed
class CustomLabelsView(BaseLoggedInPage):
    """This view represents Custom labels tab page"""

    paginator = Pagination(
        locator='.//div[contains(@class, "pf-c-pagination")]')
    add_label_button = Button("Add label")
    search = Input(locator=".//input[@aria-label='Filter by short path']")
    table_loading = './/div[contains(@class, "pf-c-skeleton")]'
    ACTIONS_INDEX = 2
    table = Table(
        locator='.//table[contains(@aria-label, "Table")]',
        column_widgets={
            "Short path":
            Button(locator='.//th[@data-label="Short path"]/button'),
            ACTIONS_INDEX: Dropdown(),
        },
    )

    def is_table_loaded(self):
        return wait_for(
            lambda: not self.browser.is_displayed(self.table_loading),
            delay=10,
            timeout=120)

    @property
    def is_displayed(self):
        if self.is_table_loaded():
            return self.add_label_button.is_displayed and self.table.is_displayed
        else:
            return False
Example #3
0
        class custom_labels(View):  # noqa
            title = Text(locator=".//h5[normalize-space(.)='Custom labels']")
            add_label_button = Button("Add label")
            upload_label = HiddenFileInput(
                locator='.//input[contains(@accept,".xml")]')
            enabled_button = Text(
                locator=".//label/span[contains(@class, 'pf-c-switch__toggle')]"
            )
            close_button = Button("Close")
            next_button = Button("Next")
            back_button = Button("Back")
            cancel_button = Button("Cancel")
            fill_strategy = WaitFillViewStrategy("15s")

            @property
            def is_displayed(self):
                return self.title.is_displayed and self.add_label_button.is_displayed

            def fill(self, values):
                """
                Args:
                    values: custom label file to be uploaded
                """
                if values.get("file_label"):
                    self.expand_custom_labels.click()
                wait_for(lambda: self.next_button.is_enabled,
                         delay=0.2,
                         timeout=60)
                was_change = True
                self.after_fill(was_change)
                return was_change

            def after_fill(self, was_change):
                self.next_button.click()
class ApplicationsView(BaseLoggedInPage):

    title = Text(locator=".//div/h2[normalize-space(.)='Applications']")
    ACTIONS_INDEX = 2
    table = PatternflyTable(
        ".//table[contains(@class, 'pf-c-table')]",
        column_widgets={
            "Application": Text(locator=".//a"),
            "Date added": Text(locator=".//td[@data-label='Date added']"),
            ACTIONS_INDEX: Dropdown(),
        },
    )
    add_application_button = Button("Add application")
    upload_file = HiddenFileInput(
        locator='.//input[@accept=".ear, .har, .jar, .rar, .sar, .war, .zip"]'
    )
    search = Input(locator=".//input[@aria-label='Filter by name']")
    done_button = Button("Close")
    application_packages = Text(
        locator=".//wu-select-packages/h3[normalize-space(.)='Application packages']"
    )
    sort_application = Text(locator=".//th[contains(normalize-space(.), 'Application')]//i[1]")
    save_and_run_button = Button("Save and run")
    delete_button = Button("Delete")
    cancel_button = Button("Cancel")

    @property
    def is_displayed(self):
        return self.add_application_button.is_displayed and self.title.is_displayed

    def clear_search(self):
        """Clear search"""
        if self.search.value:
            self.search.fill("")
Example #5
0
        class set_transformation_target(View):  # noqa
            title = Text(
                locator=
                ".//h5[normalize-space(.)='Select transformation target']")
            transformation_path = TransformationPath()
            application_packages = Text(
                locator=
                ".//wu-select-packages/h3[normalize-space(.)='Application packages']"
            )
            next_button = Button("Next")
            back_button = Button("Back")
            cancel_button = Button("Cancel")
            fill_strategy = WaitFillViewStrategy("15s")

            @property
            def is_displayed(self):
                return self.title.is_displayed and self.transformation_path.is_displayed

            def fill(self, values):
                """
                Args:
                    values: transformation path to be selected
                """
                if values.get("transformation_path"):
                    self.transformation_path.select_card(
                        card_name=values.get("transformation_path"))
                was_change = True
                wait_for(lambda: self.next_button.is_enabled,
                         delay=0.2,
                         timeout=60)
                self.after_fill(was_change)
                return was_change

            def after_fill(self, was_change):
                self.next_button.click()
class AddCustomRuleServerPathView(CustomRulesView):
    rules_path = Input(id="serverPath")
    scan_recursive = Checkbox("isChecked")
    save_button = Button("Save")
    cancel_button = Button("Cancel")

    @property
    def is_displayed(self):
        return self.rules_path.is_displayed and self.cancel_button.is_displayed
Example #7
0
        class select_packages(ParametrizedView):  # noqa
            title = Text(locator=".//h5[normalize-space(.)='Select packages']")
            PARAMETERS = ("pkg", )
            select_all_packages = Text(
                locator=".//input[@class='ant-checkbox-input']")

            packages = Text(
                ParametrizedLocator(
                    ".//div[contains(@class, 'ant-tree-treenode-switcher-close') "
                    "and not(contains(@class, 'ant-tree-treenode-disabled'))]"
                    "/span/span/span[normalize-space(.)={pkg|quote}]"))
            included_packages = Text(
                ParametrizedLocator(
                    ".//li[@class='ant-transfer-list-content-item']"
                    "/span[normalize-space(.)={pkg|quote}]"))
            move_into_button = Button(
                locator=".//span[contains(@class, 'anticon anticon-right')]")
            move_from_button = Button(
                locator=".//span[contains(@class, 'anticon anticon-left')]")

            next_button = Button("Next")
            back_button = Button("Back")
            cancel_button = Button("Cancel")
            fill_strategy = WaitFillViewStrategy("15s")

            @property
            def is_displayed(self):
                return self.title.is_displayed and self.select_all_packages.is_displayed

            def fill_pkg(self):
                """Add packages"""
                self.packages.click()
                self.move_into_button.click()
                was_change = True
                return was_change

            def remove(self):
                """Remove packages"""
                self.included_packages.click()
                self.move_from_button.click()
                was_change = True
                return was_change

            def fill(self, values):
                """
                Args:
                    values: application packages to be selected
                """
                if values.get("pkg"):
                    self.fill_pkg()
                was_change = True
                self.after_fill(was_change)
                return was_change

            def after_fill(self, was_change):
                self.next_button.click()
class AddCustomRuleView(CustomRulesView):
    title = Text(locator=".//h1[contains(normalize-space(.), 'Add rules')]")
    upload_rule = HiddenFileInput(locator='.//input[contains(@accept,".xml")]')
    browse_button = Button("Browse")
    close_button = Button("Close")
    fill_strategy = WaitFillViewStrategy("15s")

    @property
    def is_displayed(self):
        return self.title.is_displayed and self.browse_button.is_displayed
class DeleteCustomLabelView(View):
    title = Text(locator=".//h1[contains(normalize-space(.), 'Delete')]")
    fill_strategy = WaitFillViewStrategy("35s")

    delete_button = Button("Delete")
    cancel_button = Button("Cancel")

    @property
    def is_displayed(self):
        return self.title.is_displayed and self.delete_button.is_displayed
Example #10
0
class EditProjectView(AllProjectView):
    title = Text(locator=".//h1[normalize-space(.)='Project details']")
    name = Input(name="name")
    description = Input(name="description")
    save_button = Button("Save")
    cancel_button = Button("Cancel")
    fill_strategy = WaitFillViewStrategy("35s")

    @property
    def is_displayed(self):
        return self.save_button.is_displayed and self.title.is_displayed
Example #11
0
class DeleteProjectView(AllProjectView):
    title = Text(locator=".//h1[normalize-space(.)='Project details']")
    delete_project_name = Input(id="matchText")
    fill_strategy = WaitFillViewStrategy("35s")

    delete_button = Button("Delete")
    cancel_button = Button("Cancel")

    @property
    def is_displayed(self):
        return self.delete_button.is_displayed and self.title.is_displayed
class AddCustomLabelView(View):
    title = Text(locator=".//h1[contains(normalize-space(.), 'Add labels')]")
    upload_label = HiddenFileInput(
        locator='.//input[contains(@accept,".xml")]')
    file_uploaded = './/div[contains(@class, "pf-m-success")]'
    browse_button = Button("Browse")
    close_button = Button("Close")
    fill_strategy = WaitFillViewStrategy("15s")

    @property
    def is_displayed(self):
        return self.title.is_displayed and self.browse_button.is_displayed
Example #13
0
class AnalysisConfigurationView(BaseLoggedInPage):

    save_and_run_button = Button("Save and run")
    title = Text(
        locator=".//div/h1/span[normalize-space(.)='Analysis Configuration']")
    transformation_path = TransformationPath()
    select_none = Button("Select None")
    select_app_msg = Text(
        locator=".//span[normalize-space(.)= "
        "'You must select an application to run the analysis with')]")

    @property
    def is_displayed(self):
        return self.transformation_path.is_displayed and self.title.is_displayed
class AddCustomRuleView(CustomRulesView):
    title = Text(locator=".//h1[contains(normalize-space(.), 'Add rules')]")
    upload_rule = HiddenFileInput(locator='.//input[contains(@accept,".xml")]')
    file_uploaded = './/div[contains(@class, "pf-m-success")]'
    browse_button = Button("Browse")
    close_button = Button("Close")

    @View.nested
    class server_path(MTATab):  # noqa
        TAB_NAME = "Server path"
        including_view = View.include(AddCustomRuleServerPathView, use_parent=True)

    @property
    def is_displayed(self):
        return self.title.is_displayed and self.browse_button.is_displayed
Example #15
0
    class review(View):  # noqa
        title = Text(
            locator=".//h5[normalize-space(.)='Review project details']")
        save = Button("Save")
        save_and_run = Button("Save and run")
        fill_strategy = WaitFillViewStrategy("15s")

        @property
        def is_displayed(self):
            return self.title.is_displayed and self.save_and_run.is_displayed

        def after_fill(self, was_change):
            wait_for(lambda: self.save_and_run.is_enabled,
                     delay=5,
                     timeout=150)
            self.save_and_run.click()
Example #16
0
class DetailsProjectView(AllProjectView):
    run_analysis_button = Button("Run Analysis")
    title = Text(locator=".//div/h2[normalize-space(.)='Active Analysis']")

    @property
    def is_displayed(self):
        return self.run_analysis_button.is_displayed and self.title.is_displayed
Example #17
0
    class create_project(View):  # noqa
        title = Text(locator=".//h5[normalize-space(.)='Project details']")
        name = Input(name="name")
        description = Input(locator='.//textarea[@name="description"]')
        next_button = Button("Next")
        cancel_button = Button("Cancel")
        yes_button = Button("Yes")
        no_button = Button("No")
        fill_strategy = WaitFillViewStrategy("30s")

        @property
        def is_displayed(self):
            return self.name.is_displayed and self.title.is_displayed

        def after_fill(self, was_change):
            self.next_button.click()
Example #18
0
class CloudInsightsView(BaseLoggedInView, SearchableViewMixin):
    """Main RH Cloud Insights view."""

    title = Text('//h1[text()="Red Hat Insights"]')
    insights_sync_switcher = Switch('OUIA-Generated-Switch-1')
    remediate = Button('Remediate')
    insights_dropdown = Dropdown(
        locator='.//div[contains(@class, "title-dropdown")]')
    select_all = Checkbox(locator='.//input[@aria-label="Select all rows"]')
    table = PatternflyTable(
        component_id='OUIA-Generated-Table-2',
        column_widgets={
            0: Checkbox(locator='.//input[@type="checkbox"]'),
            'Hostname': Text('.//a'),
            'Recommendation': Text('.//a'),
            'Total Risk': Text('.//a'),
            'Playbook': Text('.//a'),
        },
    )
    select_all_hits = Button('Select recommendations from all pages')
    clear_hits_selection = Button('Clear Selection')
    pagination = Pagination()
    remediation_window = View.nested(RemediationView)

    @property
    def is_displayed(self):
        return self.title.wait_displayed()

    def search(self, query):
        """Perform search using searchbox on the page and return table
        contents.

        :param str query: search query to type into search field. E.g. ``foo``
            or ``name = "bar"``.
        :return: list of dicts representing table rows
        :rtype: list
        """
        if not hasattr(self.__class__, 'table'):
            raise AttributeError(
                f'Class {self.__class__.__name__} does not have attribute "table". '
                'SearchableViewMixin only works with views, which have table for results. '
                'Please define table or use custom search implementation instead'
            )
        self.searchbox.search(query + Keys.ENTER)
        self.table.wait_displayed()
        return self.table.read()
Example #19
0
class RemediationView(Modal):
    """ Remediation window view"""

    remediate = Button('Remediate')
    cancel = Button('Cancel')
    table = PatternflyTable(
        component_id='OUIA-Generated-Table-2',
        column_widgets={
            'Hostname': Text('./a'),
            'Recommendation': Text('./a'),
            'Resolution': Text('.//a'),
            'Reboot Required': Text('.//a'),
        },
    )

    @property
    def is_displayed(self):
        return self.title.wait_displayed()
Example #20
0
class CloudTokenView(BaseLoggedInView):
    """RH Cloud Insights Landing page for adding RH Cloud Token."""

    rhcloud_token = TextInput(locator='//input[contains(@aria-label, "input-cloud-token")]')
    save_token = Button('Save setting and sync recommendations')

    @property
    def is_displayed(self):
        return self.rhcloud_token.wait_displayed()
class AllProjectView(BaseLoggedInPage):
    """This view represent Project All View"""

    title = Text(".//div[contains(@class, 'pf-c-content')]/h1")
    search = Input(locator=".//input[@aria-label='Filter by name']")
    sort = SortSelector("class", "btn btn-default dropdown-toggle")
    table_loading = './/div[contains(@class, "pf-c-skeleton")]'

    ACTIONS_INDEX = 4
    table = PatternflyTable(
        ".//table[contains(@class, 'pf-c-table pf-m-grid-md')]",
        column_widgets={
            "Name": Text(locator=".//a"),
            "Applications": Text(locator=".//td[@data-label='Applications']"),
            "Status": Text(locator=".//td[@data-label='Status']"),
            "Description": Text(locator=".//td[@data-label='Description']"),
            ACTIONS_INDEX: Dropdown(),
        },
    )

    create_project = Button("Create project")

    @View.nested
    class no_matches(View):  # noqa
        """After search if no match found"""

        text = Text(".//div[contains(@class, 'pf-c-empty-state__body')]")

    def clear_search(self):
        """Clear search"""
        if self.search.value:
            self.search.fill("")

    @property
    def all_projects(self):
        """Returns list of all project rows"""
        return [row for row in self.table]

    @property
    def is_displayed(self):
        return self.is_empty or (
            self.create_project.is_displayed
            and self.title.text == "Projects"
            and self.table.is_displayed
            and self.validate_url()
        )

    def select_project(self, name):
        for row in self.table:
            if row.name.text == name:
                row["Name"].widget.click()

    def table_loaded(self):
        return wait_for(
            lambda: not self.browser.is_displayed(self.table_loading), delay=10, timeout=120
        )
class CustomLabelsView(BaseLoggedInPage):
    """This view represents Custom labels tab page"""

    paginator = Pagination(
        locator='.//div[contains(@class, "pf-c-pagination")]')
    add_label_button = Button("Add label")
    search = Input(locator=".//input[@aria-label='Filter by short path']")
    ACTIONS_INDEX = 2
    table = Table(
        locator='.//table[contains(@aria-label, "Table")]',
        column_widgets={
            "Short path":
            Button(locator='.//th[@data-label="Short path"]/button'),
            ACTIONS_INDEX: Dropdown(),
        },
    )

    @property
    def is_displayed(self):
        return self.add_label_button.is_displayed and self.table.is_displayed
Example #23
0
class AnsibleRolesImportView(BaseLoggedInView):
    """View while selecting Ansible roles to import."""

    breadcrumb = BreadCrumb()
    total_available_roles = Text(
        "//div[@class='pf-c-pagination__total-items']/b[2]")
    select_all = Checkbox(locator="//input[@id='select-all']")
    table = PatternflyTable(
        component_id='OUIA-Generated-Table-2',
        column_widgets={
            0: Checkbox(locator='.//input[@type="checkbox"]'),
        },
    )
    pagination = ImportPagination()
    submit = Button('Submit')
    cancel = Button('Cancel')

    @property
    def is_displayed(self):
        return (self.breadcrumb.locations[0] == 'Roles'
                and self.breadcrumb.read() == 'Changed Ansible roles')
Example #24
0
    class add_applications(View):  # noqa
        title = Text(locator=".//h5[normalize-space(.)='Add applications']")
        delete_application = Text(
            locator=".//button[contains(@aria-label, 'delete-application')]")
        browse_button = Button("Browse")
        progress_bar = './/div[contains(@role, "progressbar")]'
        upload_file = HiddenFileInput(
            locator=
            './/input[@accept=".ear, .har, .jar, .rar, .sar, .war, .zip"]')

        next_button = Button("Next")
        back_button = Button("Back")
        cancel_button = Button("Cancel")
        fill_strategy = WaitFillViewStrategy("20s")

        @property
        def is_displayed(self):
            return self.title.is_displayed and self.browse_button.is_displayed

        def in_progress(self):
            return self.browser.is_displayed(self.progress_bar)

        def fill(self, values):
            app_list = values.get("app_list")
            env = conf.get_config("env")
            fs = FTPClientWrapper(env.ftpserver.entities.mta)
            for app in app_list:
                # Download application file to analyze
                # This part has to be here as file is downloaded temporarily
                file_path = fs.download(app)
                self.upload_file.fill(file_path)
            wait_for(lambda: self.next_button.is_enabled,
                     delay=0.2,
                     timeout=60)
            was_change = True
            self.after_fill(was_change)
            return was_change

        def after_fill(self, was_change):
            self.next_button.click()
Example #25
0
class AnalysisResultsView(BaseLoggedInPage):

    run_analysis_button = Button("Run analysis")
    title = Text(locator=".//div/h1[normalize-space(.)='Analysis results']")
    search = Input(
        locator=".//input[@aria-label='Filter by analysis id or status']")
    analysis_results = AnalysisResults()
    confirm_delete = Button("Yes")
    cancel_delete = Button("No")
    sort_analysis = Text(
        locator=".//th[contains(normalize-space(.), 'Analysis')]//button")
    table = Table(
        locator='.//table[contains(@aria-label, "Table")]',
        column_widgets={
            "Analysis": Text(locator=".//a"),
            5: Dropdown()
        },
    )

    @property
    def is_displayed(self):
        return self.run_analysis_button.is_displayed and self.title.is_displayed

    def clear_search(self):
        """Clear search"""
        self.search.fill("")

    @ParametrizedView.nested
    class analysis_row(ParametrizedView):  # noqa
        PARAMETERS = ("row", )

        analysis_number = Text(ParametrizedLocator(".//tr[{row}]/td[1]/a"))
        delete_analysis = Text(
            ParametrizedLocator(
                ".//tr[{row}]//button[@class='pf-c-button pf-m-link']"))

        @property
        def is_displayed(self):
            return self.analysis_number.is_displayed
class BlankStateView(View):
    """This view represent web-console without any project i.e. blank state"""

    ROOT = ".//div[contains(@class, 'pf-c-empty-state__content')]"

    title = Text(locator=".//h4")
    welcome_help = Text(locator=".//div[@class='welcome-help-text']")
    create_project = Button("Create project")
    documentation = Text(locator=".//a[contains(text(), 'documentation')]")

    @property
    def is_displayed(self):
        return (self.title.is_displayed and self.title.text
                == "Welcome to the Migration Toolkit for Applications"
                and self.create_project.is_displayed)
class SystemLabelsView(BaseLoggedInPage):
    """This view represents System labels tab page"""

    paginator = Pagination(
        locator='.//div[contains(@class, "pf-c-pagination")]')

    table = Table(
        locator='.//table[contains(@aria-label, "Table")]',
        column_widgets={
            "Provider ID":
            Button(locator='.//th[@data-label="Provider ID"]/button'),
            2:
            Dropdown(),
        },
    )
    search = Input(locator='.//input[@aria-label="Filter by provider ID"]')

    @property
    def is_displayed(self):
        return self.table.is_displayed and self.search.is_displayed
Example #28
0
class AnsibleRolesView(BaseLoggedInView, SearchableViewMixin):
    """Main Ansible Roles view. Prior to importing any roles, only the import_button
    is present, without the search widget or table.
    """

    title = Text("//h1[contains(., text()='Ansible Roles')")
    import_button = Text("//a[contains(@href, '/ansible_roles/import')]")
    submit = Button('Submit')
    total_imported_roles = Text(
        ".//span[contains(@class, 'pagination-pf-items-total')]")
    table = Table(
        './/table',
        column_widgets={
            'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"),
        },
    )
    pagination = Pagination()

    @property
    def is_displayed(self):
        return self.title.is_displayed and self.import_button.is_displayed
class BaseLoggedInPage(View):
    """This is base view for MTA"""

    header = Text(locator=".//img[@alt='brand']")
    navigation = MTANavigation(locator='//ul[@class="pf-c-nav__list"]')
    logout_button = Dropdown(text="mta")

    setting = DropdownMenu(
        locator=
        ".//li[contains(@class, 'dropdown') and .//span[@class='pficon pficon-user']]"
    )
    help = Button(id="aboutButton")

    # only if no project available
    blank_state = View.nested(BlankStateView)

    def validate_url(self):
        """The logged in Page in both web console and operator are same
        so added a url check to differentiate in the view"""
        if self.context["object"].application.mta_context == "ViaOperatorUI":
            url = self.context["object"].application.ocphostname
        elif self.context["object"].application.mta_context == "ViaSecure":
            url = self.context["object"].application.ocpsecurehostname
        elif self.context["object"].application.mta_context == "ViaWebUI":
            url = self.context["object"].application.hostname
        return url in self.context[
            "object"].application.web_ui.widgetastic_browser.url

    @property
    def is_empty(self):
        """Check project is available or not; blank state"""
        return self.blank_state.is_displayed and self.validate_url()

    @property
    def is_displayed(self):
        return self.header.is_displayed and self.help.is_displayed and self.validate_url(
        )

    def logout(self):
        self.view.logout_button.item_select("Logout")
Example #30
0
class AllApplicationsView(BaseLoggedInPage):
    """Class for All applications view"""

    filter_application = FilterInput(id="filter")
    title = Text(
        locator=".//div[text()[normalize-space(.)='Application List']]")
    send_feedback = Text(locator=".//a[contains(text(), 'Send Feedback')]")

    clear = Text('.//a[@id="clear-filters"]')

    filter_selector = DropdownMenu(
        locator='.//span[@class="filter-by"]/parent::button/parent::div')
    application_table = ApplicationList()

    sort_selector = DropdownMenu(
        locator='.//span[@id="sort-by"]/parent::button/parent::div')
    alpha_sort = Button(locator=".//button[@id='sort-order']")

    def clear_filters(self):
        if self.clear.is_displayed:
            self.clear.click()

    def search(self, search_value, filter_type="Name", clear_filters=False):
        """Fill input box with 'search_value', use 'filter_type' to choose filter selector.
        If no filter_type is entered then the default for page is used.
        """
        if clear_filters:
            self.clear_filters()
        if filter_type:
            self.filter_selector.item_select(filter_type)
        self.filter_application.fill(search_value)

    def sort_by(self, sort_criteria):
        """
        Select the sort criteria among Name and Story Points to sort applications list
        """
        if self.sort_selector.items is not None:
            self.sort_selector.item_select(sort_criteria)

    @property
    def is_displayed(self):
        return self.filter_application.is_displayed and self.title.is_displayed

    @View.nested
    class tabs(View):  # noqa
        """The tabs on the page"""
        @View.nested
        class issues(MTATab):  # noqa
            fill_strategy = WaitFillViewStrategy("15s")
            TAB_NAME = "Issues"
            including_view = View.include(Issues, use_parent=True)

        @View.nested
        class technologies(MTATab):  # noqa
            fill_strategy = WaitFillViewStrategy("20s")
            TAB_NAME = "Technologies"
            title = Text('.//div[contains(@class, "page-header")]/h1/div')

            @property
            def is_displayed(self):
                return self.title.text == "Technologies"

        @View.nested
        class hard_coded_ip(MTATab):  # noqa
            fill_strategy = WaitFillViewStrategy("20s")
            TAB_NAME = "Hard-coded IP Addresses"
            including_view = View.include(HardCodedIP, use_parent=True)