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
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("")
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
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
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
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
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
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()
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
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()
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()
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()
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
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')
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()
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
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")
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)