class OrganizationsView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[normalize-space(.)='Organizations']") new = Text("//a[contains(@href, '/organizations/new')]") table = Table( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }, ) @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class ProductsTableView(BaseLoggedInView, SearchableViewMixin): title = Text("//h2[contains(., 'Products')]") new = Text("//button[contains(@href, '/products/new')]") edit = Text( "//td/a[contains(@ui-sref, 'product.repositories') and contains(@href, 'products')]" ) repo_discovery = Text("//button[contains(.,'Repo Discovery')]") actions = ActionsDropdown("//div[contains(@class, 'btn-group')]") table = Table('.//table', column_widgets={'Name': Text('./a')}) dialog = ConfirmationDialog() @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class ProvisioningTemplatesView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[text()='Provisioning Templates']") new = Button("Create Template") build_pxe_default = Button("Build PXE Default") table = Table( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }, ) @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class VirtwhoConfiguresView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[text()='Virt-who Configurations']") new = Text("//a[contains(@href, '/foreman_virt_who_configure/configs/new')]") table = Table( './/table', column_widgets={ 'Name': Text('./a'), 'Status': VirtwhoConfigureStatus('.'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }, ) @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class ProvisioningTemplatesView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[contains(., 'Provisioning Templates')]") new = Text("//a[contains(@href, '/templates/provisioning_templates/new')]") table = SatTable( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), } ) @property def is_displayed(self): return self.browser.wait_for_element( self.title, exception=False) is not None
class plan(ParametrizedView): """Parametrized view for a nested plan view. Takes plan name on instantiation""" PARAMETERS = ("plan_name", ) ROOT = ParametrizedLocator( ".//h2[contains(normalize-space(.), {plan_name|quote})]/" "ancestor::div[contains(@id, 'maintenance-plan')]") title = Text(".") delete = Text(".//i[@tooltip='Delete this plan']") edit = Text(".//i[@tooltip='Click to edit this plan']") ansible_actions = ActionsDropdown( "//div[contains(@class, 'btn-group')][@ng-if='ansibleRunner']") export_csv = Button("Export CSV") add_actions = Button("Add actions")
class SCAPPoliciesView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[text()='Compliance Policies']") new = Text("//a[contains(@href, '/compliance/policies/new')]") table = SatTable( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), } ) @property def is_displayed(self): return self.browser.wait_for_element( self.title, exception=False) is not None
class ComputeProfilesView(BaseLoggedInView, SearchableViewMixin): title = Text('//*[(self::h1 or self::h5) and text()="Compute Profiles"]') new = Text('//a[text()="Create Compute Profile"]') table = Table( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }, ) @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class PartitionTablesView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[text()='Partition Tables']") new = Button("Create Partition Table") table = SatTable( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }) @property def is_displayed(self): return self.browser.wait_for_element( self.title, exception=False) is not None
class HostDetailsView(BaseLoggedInView): breadcrumb = BreadCrumb() @property def is_displayed(self): breadcrumb_loaded = self.browser.wait_for_element( self.breadcrumb, exception=False) return ( breadcrumb_loaded and self.breadcrumb.locations[0] == 'All Hosts' and self.breadcrumb.read() != 'Create Host' ) boot_disk = ActionsDropdown( "//div[contains(@class, 'btn-group')]" "[contains(., 'Boot')][not(*[self::div])]" ) schedule_remote_job = ActionsDropdown( "//div[contains(@class, 'btn-group')]" "[contains(., 'Schedule')][not(*[self::div])]" ) back = Text("//a[text()='Back']") edit = Text("//a[@id='edit-button']") clone = Text("//a[@id='clone-button']") build = Text("//a[@id='build-review']") delete = Text("//a[@id='delete-button']") audits_details = Text("//a[text()='Audits']") facts_details = Text("//a[text()='Facts']") yaml_dump = Text("//a[text()='YAML']") yaml_output = Text("//pre") content_details = Text("//a[text()='Content']") @View.nested class properties(SatTab): properties_table = SatTableWithUnevenStructure( locator="//table[@id='properties_table']")
class ReportTemplatesView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[normalize-space(.)='Report Templates']") new = Button("Create Template") table = Table( './/table', column_widgets={ 'Name': Text('./a'), 'Locked': Text('.'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }, ) @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class SCAPContentsView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[text()='SCAP Content']") new = Text("//a[contains(@href, 'scap_contents/new')]") table = SatTable( './/table', column_widgets={ 'Title': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }, ) dialog = Pf4ConfirmationDialog() @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class TrendsView(BaseLoggedInView): title = Text("//h1[text()='Trends']") welcome_page = Text("//div[@class='blank-slate-pf']") new = Text("//a[contains(@href, '/trends/new')]") table = Table('.//table', column_widgets={ 'Name': Text('./a'), 'Action': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }) @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class overview(SatTab): job_status = Text( "//div[@id='job_invocations_chart_container']" "//*[name()='tspan'][contains(@class,'donut-title-small-pf')]") job_status_progress = Text( "//div[@id='job_invocations_chart_container']" "//*[name()='tspan'][contains(@class,'donut-title-big-pf')]") hosts_table = SatTable( './/table', column_widgets={ 'Host': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }) total_hosts = Text("//h2[contains(., 'Total hosts')]" "/span[@class='card-pf-aggregate-status-count']")
class HostGroupsView(BaseLoggedInView, SearchableViewMixin): title = Text( "//h1[contains(., 'Host Group Configuration') or text()='Host Groups']" ) new = Text("//a[contains(@href, '/hostgroups/new')]") table = SatTable( './/table', column_widgets={ 'Name': Text("./a"), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }) @property def is_displayed(self): return (self.browser.wait_for_element(self.title, exception=False) is not None and self.browser.url.endswith('hostgroups'))
class ComputeResourcesView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[text()='Compute Resources']") new = Text("//a[contains(@href, '/compute_resources/new')]") table = SatTable( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }) @property def is_displayed(self): """Check if the right page is displayed""" return self.browser.wait_for_element(self.title, exception=False) is not None
class DiscoveryRulesView(BaseLoggedInView): title = Text("//h1[text()='Discovery Rules']") new = Text("//a[contains(@href, '/discovery_rules/new')]") table = SatTable( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown( "./div[contains(@class, 'btn-group')]"), } ) @property def is_displayed(self): return self.browser.wait_for_element( self.title, exception=False) is not None
class SCAPTailoringFilesView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[text()='Tailoring Files']") new = Text("//a[contains(@href, 'tailoring_files/new')]") table = Table( './/table', column_widgets={ 'Name': Text("./a[contains(@href, '/compliance/tailoring_files')]"), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }, ) dialog = Pf4ConfirmationDialog() @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class TasksView(BaseLoggedInView, SearchableViewMixin): title = Text("//h1[text()='Tasks']") focus = ActionsDropdown( "//div[./button[@id='tasks-dashboard-time-period-dropdown']]") table = SatTable( ".//div[@class='tasks-table']//table", column_widgets={ 'Action': Text('./a'), }, ) pagination = TaskPagination() @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None @View.nested class RunningChart(View): ROOT = ".//div[@id='running-tasks-card']" name = Text("./h2") total = PieChart("./div[@class='card-pf-body']") @View.nested class PausedChart(View): ROOT = ".//div[@id='paused-tasks-card']" name = Text("./h2") total = PieChart("./div[@class='card-pf-body']") @View.nested class StoppedChart(View): ROOT = ".//div[@id='stopped-tasks-card']" name = Text("./h2") table = Table( locator='.//table', column_widgets={ 'Total': Text('./button'), }, ) @View.nested class ScheduledChart(View): ROOT = ".//div[@id='scheduled-tasks-card']" name = Text("./h2") total = Text(".//div[@class='scheduled-data']")
class ActivationKeyEditView(BaseLoggedInView): """View for the ActivationKeys Edit page""" breadcrumb = BreadCrumb() actions = ActionsDropdown("//div[contains(@class, 'btn-group')]") dialog = ConfirmationDialog() @property def is_displayed(self): breadcrumb_loaded = self.browser.wait_for_element(self.breadcrumb, exception=False) return (breadcrumb_loaded and self.breadcrumb.locations[0] == 'Activation Keys' and self.breadcrumb.read() != 'New Activation Key') @View.nested class details(SatTab): name = EditableEntry(name='Name') description = EditableEntry(name='Description') hosts_limit = EditableLimitEntry(name='Host Limit') service_level = EditableEntrySelect(name='Service Level') lce = ParametrizedView.nested(LCESelectorGroup) content_view = EditableEntrySelect(name='Content View') @View.nested class subscriptions(SatTab): resources = View.nested(AddRemoveSubscriptionsView) @View.nested class repository_sets(SatTab): TAB_NAME = 'Repository Sets' table = Table(locator=".//table") @View.nested class host_collections(SatTab): TAB_NAME = 'Host Collections' resources = View.nested(AddRemoveResourcesView) @View.nested class content_hosts(SatTabWithDropdown): TAB_NAME = 'Associations' SUB_ITEM = 'Content Hosts' table = Table(locator=".//table")
class ComputeResourcesView(BaseLoggedInView, SearchableViewMixin): title = Text( '//*[(self::h1 or self::h5) and normalize-space(.)="Compute Resources"]' ) new = Text('//a[normalize-space(.)="Create Compute Resource"]') table = SatTable( './/table', column_widgets={ 'Name': Text('./a'), 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }, ) @property def is_displayed(self): """Check if the right page is displayed""" return self.browser.wait_for_element(self.title, exception=False) is not None
class ContentHostsView(BaseLoggedInView, SearchableViewMixin): title = Text("//h2[contains(., 'Content Hosts')]") export = Text( ".//a[contains(@class, 'btn')][contains(@href, 'content_hosts.csv')]") register = Text(".//button[@ui-sref='content-hosts.register']") actions = ActionsDropdown(".//div[contains(@class, 'btn-group')]") dialog = ConfirmationDialog() table = SatTable('.//table', column_widgets={ 0: Checkbox(locator="./input[@type='checkbox']"), 'Name': Text('./a'), 'Subscription Status': StatusIcon(), 'Installable Updates': InstallableUpdatesCellView(), }) @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class ProductEditView(BaseLoggedInView): breadcrumb = BreadCrumb() actions = ActionsDropdown("//div[contains(@class, 'btn-group')]") dialog = ConfirmationDialog() @property def is_displayed(self): breadcrumb_loaded = self.browser.wait_for_element(self.breadcrumb, exception=False) return (breadcrumb_loaded and self.breadcrumb.locations[0] == 'Products' and self.breadcrumb.read() not in ('New Product', 'Discover Repositories') and len(self.breadcrumb.locations) <= 3) @View.nested class details(SatTab): name = EditableEntry(name='Name') label = ReadOnlyEntry(name='Label') gpg_key = EditableEntrySelect(name='GPG Key') ssl_ca_cert = EditableEntrySelect(name='SSL CA Cert') ssl_client_cert = EditableEntrySelect(name='SSL Client Cert') ssl_client_key = EditableEntrySelect(name='SSL Client Key') description = EditableEntry(name='Description') repos_count = ReadOnlyEntry(name='Number of Repositories') tasks_count = ReadOnlyEntry(name='Active Tasks') sync_plan = EditableEntrySelect(name='Sync Plan') sync_state = ReadOnlyEntry(name='Sync State') @View.nested class repositories(SatTab): table = SatTable( locator=".//table", column_widgets={ 0: Checkbox( locator="./input[@ng-change='itemSelected(repository)']"), 'Name': Text("./a"), }, )
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 HostCollectionInstallErrataView(BaseLoggedInView, SearchableViewMixin): title = Text("//h4[contains(., 'Content Host Errata Management')]") search = TextInput( locator=".//input[@type='text' and @ng-model='errataFilter']") refresh = Text(locator=".//button[@ng-click='fetchErrata()']") install = ActionsDropdown("//span[contains(@class, 'btn-group')]" "[button[contains(@class, 'btn') " "and contains(@ng-click, 'showConfirm')]]") table = SatTable( ".//table", column_widgets={ 0: Checkbox(locator=".//input[@ng-model='erratum.selected']"), 'Id': Text(locator="./a[@ng-click='transitionToErrata(erratum)']"), }) dialog = ConfirmationDialog() @property def is_displayed(self): """The view is displayed when it's title exists""" return self.browser.wait_for_element(self.title, exception=False) is not None
class repository_sets(SatTab, SearchableViewMixin): TAB_NAME = 'Repository Sets' show_all = Checkbox(locator=".//input[contains(@ng-model, 'contentAccessModeAll')]") limit_to_lce = Checkbox(locator=".//input[contains(@ng-model, 'contentAccessModeEnv')]") actions = ActionsDropdown("//div[contains(@class, 'btn-group')]") table = SatTable( './/table', column_widgets={ 0: Checkbox(locator="./input[@type='checkbox']"), 'Product Name': Text('./a'), }, ) def read(self): """Sometimes no checkboxes are checked off by default, selecting "Show All" in such case. """ if self.show_all.read() is False and self.limit_to_lce.read() is False: self.show_all.fill(True) return super().read()
class ProductEditView(BaseLoggedInView): return_to_all = Text("//a[text()='Products']") actions = ActionsDropdown("//div[contains(@class, 'btn-group')]") dialog = ConfirmationDialog() @property def is_displayed(self): return self.browser.wait_for_element(self.return_to_all, exception=False) is not None @View.nested class details(SatTab): name = EditableEntry(name='Name') label = ReadOnlyEntry(name='Label') gpg_key = EditableEntrySelect(name='GPG Key') ssl_ca_cert = EditableEntrySelect(name='SSL CA Cert') ssl_client_cert = EditableEntrySelect(name='SSL Client Cert') ssl_client_key = EditableEntrySelect(name='SSL Client Key') description = EditableEntry(name='Description') repos_count = ReadOnlyEntry(name='Number of Repositories') tasks_count = ReadOnlyEntry(name='Active Tasks') sync_plan = EditableEntrySelect(name='Sync Plan')
class HostCollectionEditView(BaseLoggedInView): breadcrumb = BreadCrumb() actions = ActionsDropdown("//div[contains(@class, 'btn-group')]") dialog = ConfirmationDialog() @property def is_displayed(self): breadcrumb_loaded = self.browser.wait_for_element(self.breadcrumb, exception=False) return ( breadcrumb_loaded and self.breadcrumb.locations[0] == 'Host Collections' and self.breadcrumb.read() != 'New Host Collection' ) @View.nested class details(SatTab): name = EditableEntry(name='Name') description = EditableEntry(name='Description') content_hosts = ReadOnlyEntry( locator=( ".//dt[contains(., 'Content Hosts')]/following-sibling" "::dd/a[not(contains(@class, 'ng-hide'))][1]" ) ) content_host_limit = EditableLimitEntry(name='Content Host Limit') # Package Installation, Removal, and Update manage_packages = Text(".//a[@ng-click='openPackagesModal()']") # Errata Installation install_errata = Text(".//a[@ng-click='openErrataModal()']") # Module Stream Installation, Removal, and Update manage_module_streams = Text(".//a[@ng-click='openModuleStreamsModal()']") # Change assigned Lifecycle Environment or Content View change_assigned_content = Text(".//a[@ng-click='openEnvironmentModal()']") @View.nested class hosts(SatTab): TAB_NAME = 'Hosts' resources = View.nested(AddRemoveResourcesView)
class ActivationKeyEditView(BaseLoggedInView): return_to_all = Text("//a[text()='Activation Keys']") actions = ActionsDropdown("//div[contains(@class, 'btn-group')]") dialog = ConfirmationDialog() @property def is_displayed(self): return self.browser.wait_for_element(self.return_to_all, exception=False) is not None @View.nested class details(SatTab): name = EditableEntry(name='Name') description = EditableEntry(name='Description') hosts_limit = EditableLimitEntry(name='Host Limit') service_level = EditableEntrySelect(name='Service Level') lce = ParametrizedView.nested(LCESelectorGroup) content_view = EditableEntrySelect(name='Content View') @View.nested class subscriptions(SatTab): resources = View.nested(AddRemoveSubscriptionsView) @View.nested class repository_sets(SatTab): TAB_NAME = 'Repository Sets' table = SatTable(locator=".//table") @View.nested class host_collections(SatTab): TAB_NAME = 'Host Collections' resources = View.nested(AddRemoveResourcesView) @View.nested class content_hosts(SatTabWithDropdown): TAB_NAME = 'Associations' SUB_ITEM = 'Content Hosts' table = SatTable(locator=".//table")
class errata(SatTab): lce_filter = Select( locator='.//select[@ng-model="selectedErrataOption"]') searchbox = Search() apply_selected = ActionsDropdown( ".//span[contains(@class, 'btn-group')]") recalculate = Button('Recalculate') table = SatTable( './/table', column_widgets={ 0: Checkbox(locator="./input[@type='checkbox']"), 'Id': Text('./a'), }, ) select_all = Checkbox( locator=".//input[@type='checkbox'][@ng-change='allSelected()']") pagination = Pagination() def search(self, query, lce=None): """Apply available filters before proceeding with searching and automatically set proper search mask if errata id instead of errata title was passed. :param str query: search query to type into search field. Both errata id (RHEA-2012:0055) and errata title (Sea_Erratum) are supported. :param str optional lce: filter by lifecycle environment :return: list of dicts representing table rows :rtype: list """ if lce is not None: self.lce_filter.fill(lce) if re.search(r'\w{4}-\d{4}:\d{4}', query): query = f'id = {query}' self.searchbox.search(query) return self.table.read()