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'), }) 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 = 'id = {}'.format(query) self.searchbox.search(query) return self.table.read()
class repositories(SatTab): lce_filter = SatSelect(".//select[@ng-model='environmentFilter']") cv_filter = SatSelect(".//select[@ng-model='contentViewFilter']") searchbox = Search() table = SatTable(locator=".//table", column_widgets={ 'Name': Text("./a"), 'Product': Text("./a"), }) def search(self, query, lce=None, cv=None): """Apply available filters before proceeding with searching. :param str query: search query to type into search field. :param str optional lce: filter by lifecycle environment name :param str optional cv: filter by content view name :return: list of dicts representing table rows :rtype: list """ if lce: self.lce_filter.fill(lce) if cv: self.cv_filter.fill(cv) self.searchbox.search(query) return self.table.read()
class AddTab(SatSecondaryTab): TAB_NAME = 'Add' searchbox = Search() add_button = Text('.//div[@data-block="list-actions"]' '//button[contains(@ng-click, "add")]') table = Table(locator=".//table") columns_exists = Text( ".//table//*[contains(name(), 'body')]/tr[1]/td[2]") def search(self, value): self.searchbox.search(value) return self.browser.element(self.parent_view.checkbox_locator % value) def add(self, value): checkbox = self.search(value) checkbox.click() self.add_button.click() def fill(self, values): if not isinstance(values, list): values = list((values, )) for value in values: self.add(value) def read(self): if self.columns_exists.is_displayed: return self.parent_view.table_labels(self.table) return []
class SearchableViewMixin(WidgetMixin): """Mixin which adds :class:`airgun.widgets.Search` widget and :meth:`search` to your view. It's useful for _most_ entities list views where searchbox is present. Note that you can override expected result locator for the element which is returned by :meth:`search` by specifying custom ``search_result_locator`` string variable in your view class. """ searchbox = Search() search_result_locator = "//a[contains(., '%s')]" def search(self, query, expected_result=None): """Perform search using searchbox on the page and return element text if found. :param str query: search query to type into search field. E.g. ``foo`` or ``name = "bar"``. :param str optional expected_result: expected resulting entity name. Useful when you specify custom search query, not just entity name. Defaults to ``query``. :return: name of entity (if found) or None :rtype: str or None """ self.searchbox.search(query) try: result = self.browser.element(self.search_result_locator % (expected_result or query)).text except NoSuchElementException: result = None return result
class content_hosts(SatTab): TAB_NAME = 'Content Hosts' environment_filter = SatSelect( ".//select[@ng-model='environmentFilter']") searchbox = Search() apply = Text(".//button[@ng-click='goToNextStep()']") table = SatTable( locator=".//table", column_widgets={ 0: Checkbox(locator="./input[@type='checkbox']"), 'Name': Text("./a"), }, ) def search(self, query, environment=None): """Apply environment filter before proceeding with searching. :param str query: search query to type into search field. :param str optional environment: filter by environment name :return: list of dicts representing table rows :rtype: list """ if environment: self.environment_filter.fill(environment) self.searchbox.search(query) return self.table.read()
class ApplyErrataView(BaseLoggedInView): breadcrumb = BreadCrumb() environment_filter = SatSelect(".//select[@ng-model='environmentFilter']") searchbox = Search() next_button = Text(".//button[@ng-click='goToNextStep()']") table = SatTable(locator=".//table", column_widgets={ 0: Checkbox(locator="./input[@type='checkbox']"), 'Name': Text("./a"), }) def search(self, query, environment=None): """Apply environment filter before proceeding with searching. :param str query: search query to type into search field. :param str optional environment: filter by environment name :return: list of dicts representing table rows :rtype: list """ if environment: self.environment_filter.fill(environment) self.searchbox.search(query) return self.table.read() @property def is_displayed(self): breadcrumb_loaded = self.browser.wait_for_element(self.breadcrumb, exception=False) return (breadcrumb_loaded and self.breadcrumb.locations[0] == 'Errata' and self.breadcrumb.read() == 'Select Content Host(s)')
class ListRemoveTab(SatSecondaryTab): TAB_NAME = 'List/Remove' searchbox = Search() remove_button = Text('.//button[@ng-click="removeSelected()"]') table = Table(locator=".//table") no_rows = Text(".//table//span[@data-block='no-rows-message' or " "@data-block='no-search-results-message']") def search(self, value): self.searchbox.search(value) return self.browser.element(self.parent_view.checkbox_locator % value) def remove(self, value): checkbox = self.search(value) checkbox.click() self.remove_button.click() def fill(self, values): if not isinstance(values, list): values = list((values, )) for value in values: self.remove(value) def read(self): if self.no_rows.is_displayed: return [] return [row.name.text for row in self.table.rows()]
class versions(SatTab): searchbox = Search() table = SatTable( locator='//table', column_widgets={ 'Version': Text('.//a'), 'Status': PublishPromoteProgressBar(), 'Actions': ActionsDropdown('./div[contains(@class, "btn-group")]') }, ) def search(self, version_name): """Searches for content view version. Searchbox can't search by version name, only by id, that's why in case version name was passed, it's transformed into recognizable value before filling, for example:: 'Version 1.0' -> 'version = 1' """ search_phrase = version_name if version_name.startswith('V') and '.' in version_name: search_phrase = 'version = {}'.format( version_name.split()[1].split('.')[0]) self.searchbox.search(search_phrase) return self.table.read()
class SearchableViewMixin(WTMixin): """Mixin which adds :class:`airgun.widgets.Search` widget and :meth:`search` to your view. It's useful for _most_ entities list views where searchbox and results table are present. Note that class which uses this mixin should have :attr:`table` attribute. """ searchbox = Search() 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 """ self.searchbox.search(query) if not hasattr(self, 'table'): raise AttributeError( 'Class {} does not have attribute "table". SearchableViewMixin' ' only works with views, which have table for results. Please ' 'define table or use custom search implementation instead' .format(self.__class__.__name__) ) return self.table.read()
class AddTab(SatSecondaryTab): TAB_NAME = 'Add' searchbox = Search() add_button = Text( './/div[@data-block="list-actions"]' '//button[contains(@ng-click, "add")]' ) def search(self, value): self.searchbox.search(value) return self.browser.element( self.parent_view.checkbox_locator % value) def add(self, value): checkbox = self.search(value) checkbox.click() self.add_button.click() def fill(self, values): if not isinstance(values, list): values = list((values,)) for value in values: self.add(value) def read(self): return self.parent_view.table.read()
class ListRemoveTab(SatSecondaryTab): """'List/Remove' tab, part of :class:`AddRemoveResourcesView`.""" TAB_NAME = 'List/Remove' searchbox = Search() remove_button = Text('.//div[@data-block="list-actions"]' '//button[contains(@ng-click, "remove")]') table = SatTable( locator=".//table", column_widgets={0: Checkbox(locator=".//input[@type='checkbox']")}) def search(self, value): """Search for specific associated resource and return the results""" self.searchbox.search(value) return self.table.read() def remove(self, value): """Remove specific associated resource""" self.search(value) next(self.table.rows())[0].widget.fill(True) self.remove_button.click() def fill(self, values): """Remove associated resource(s).""" if not isinstance(values, list): values = list((values, )) for value in values: self.remove(value) def read(self): """Return a list of associated resources""" return self.table.read()
class FiltersView(BaseLoggedInView): breadcrumb = BreadCrumb() searchbox = Search() new = Text("//a[contains(@href, '/filters/new')]") table = SatTable( ".//table", column_widgets={ 'Actions': ActionsDropdown("./div[contains(@class, 'btn-group')]"), }) @property def is_displayed(self): breadcrumb_loaded = self.browser.wait_for_element(self.breadcrumb, exception=False) return (breadcrumb_loaded and self.breadcrumb.locations[0] == 'Roles' and self.breadcrumb.read().endswith(' filters')) def search(self, query): value = self.searchbox.read() role_id = [int(s) for s in value.split() if s.isdigit()] if len(role_id) > 0: query = 'role_id = {} and resource = "{}"'.format( role_id[0], query) self.searchbox.search(query) return self.table.read()
class AddTab(SatSecondaryTab): TAB_NAME = 'Add' searchbox = Search() add_button = Text('.//div[@data-block="list-actions"]' '//button[contains(@ng-click, "add")]') table = SatTable( locator=".//table", column_widgets={0: Checkbox(locator=".//input[@type='checkbox']")}) def search(self, value): """Search for specific available resource and return the results""" self.searchbox.search(value) return self.table.read() def add(self, value): """Associate specific resource""" self.search(value) next(self.table.rows())[0].widget.fill(True) self.add_button.click() def fill(self, values): """Associate resource(s)""" if not isinstance(values, list): values = list((values, )) for value in values: self.add(value) def read(self): """Return a list of available resources""" return self.table.read()
class ArchitectureView(BaseLoggedInView): title = Text("//h1[text()='Architectures']") new = Text("//a[contains(@href, '/architectures/new')]") edit = Text("//a[contains(@href, 'edit') and contains(@href, 'arch')]") searchbox = Search() @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class affected_repositories(SatSecondaryTab): TAB_NAME = 'Affected Repositories' filter_toggle = RadioGroup(".//div[@class='col-sm-8']") product_filter = Select(locator=".//select[@ng-model='product']") searchbox = Search() update_repositories = Button('Update Repositories') table = SatTable( locator='//table', column_widgets={0: Checkbox(locator=".//input[@type='checkbox']")}, )
class OperatingSystemView(BaseLoggedInView): title = Text("//h1[text()='Operating systems']") new = Text("//a[contains(@href, '/operatingsystems/new')]") delete = GenericLocatorWidget( "//span[contains(@class, 'btn')]/a[@data-method='delete']") searchbox = Search() @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class AuditsView(BaseLoggedInView): title = Text("//h1[text()='Audits']") searchbox = Search() entry = AuditEntry() @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None def search(self, query): self.searchbox.search(query) return self.entry.read()
class ErratumView(BaseLoggedInView): title = Text("//h1[contains(., 'Errata')]") table = SatTable( locator='.//table', column_widgets={ 0: Checkbox(locator=".//input[@type='checkbox']"), 'Errata ID': Text("./a"), } ) repo_filter = SatSelect(".//select[@ng-model='repository']") applicable_filter = Checkbox( locator=".//input[@ng-model='showApplicable']") installable_filter = Checkbox( locator=".//input[@ng-model='showInstallable']") apply_errata = Text( ".//button[contains(@class, 'btn-primary')]" "[@ng-click='goToNextStep()']" ) searchbox = Search() def search(self, query, applicable=True, installable=False, repo=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 bool applicable: filter by only applicable errata :param bool installable: filter by only installable errata :param str optional repo: filter by repository name :return: list of dicts representing table rows :rtype: list """ self.installable_filter.fill(installable) self.applicable_filter.fill(applicable) if repo is not None: self.repo_filter.fill(repo) if re.search(r'\w{4}-\d{4}:\d{4}', query): query = 'id = {}'.format(query) self.searchbox.search(query) return self.table.read() @property def is_displayed(self): return self.browser.wait_for_element( self.title, exception=False) is not None
class EntitySearchView(SatSecondaryTab): repo_filter = SatSelect(".//select[@ng-model='repository']") searchbox = Search() table = Table(".//table") def search(self, query, repo=None): """Apply available filters before proceeding with searching. :param str query: search query to type into search field. :param str optional repo: filter by repository name :return: list of dicts representing table rows :rtype: list """ if repo: self.repo_filter.fill(repo) self.searchbox.search(query) return self.table.read()
class puppet_modules(SatTab): TAB_NAME = 'Puppet Modules' cv_filter = SatSelect(".//select[@ng-model='contentView']") searchbox = Search() table = SatTable(locator=".//table") def search(self, query, cv=None): """Apply available filters before proceeding with searching. :param str query: search query to type into search field. :param str optional cv: filter by content view name :return: list of dicts representing table rows :rtype: list """ if cv: self.cv_filter.fill(cv) self.searchbox.search(query) return self.table.read()
class PackagesView(BaseLoggedInView): """Main Packages view""" title = Text("//h2[contains(., 'Packages')]") table = SatTable('.//table', column_widgets={'RPM': Text("./a")}) repository = Select(locator=".//select[@ng-model='repository']") applicable = Checkbox(locator=".//input[@ng-model='showApplicable']") upgradable = Checkbox(locator=".//input[@ng-model='showUpgradable']") search_box = Search() def search(self, query, repository='All Repositories', applicable=False, upgradable=False): """Perform search using search box on the page and return table contents. :param str query: search query to type into search field. E.g. ``name = "bar"``. :param str repository: repository name to select when searching for the package. :param bool applicable: To show only applicable packages :param bool upgradable: To show only upgradable packages :return: list of dicts representing table rows :rtype: list """ self.repository.fill(repository) # set the upgradable first as if enabled, applicable element will be # disabled self.upgradable.fill(upgradable) if not upgradable: self.applicable.fill(applicable) self.search_box.search(query) return self.table.read() @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 SearchableViewMixin(WTMixin): """Mixin which adds :class:`airgun.widgets.Search` widget and :meth:`airgun.widgets.Search.search` to your view. It's useful for _most_ entities list views where searchbox and results table are present. Note that class which uses this mixin should have :attr: `table` attribute. """ searchbox = Search() welcome_message = Text("//div[@class='blank-slate-pf' or @id='welcome']") def is_searchable(self): """Verify that search procedure can be executed against specific page. That means that we have search field present on the page and that page is not a welcome one """ if self.searchbox.search_field.is_displayed and ( not self.welcome_message.is_displayed): return True return False 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' ) if not self.is_searchable(): return None self.searchbox.search(query) return self.table.read()
class packages(SatTab): cv_filter = SatSelect(".//select[@ng-model='contentView']") repo_filter = SatSelect(".//select[@ng-model='repository']") searchbox = Search() table = SatTable(locator=".//table") def search(self, query, cv=None, repo=None): """Apply available filters before proceeding with searching. :param str query: search query to type into search field. :param str optional cv: filter by content view name :param str optional repo: filter by repository name :return: list of dicts representing table rows :rtype: list """ if cv: self.cv_filter.fill(cv) if repo: self.repo_filter.fill(repo) self.searchbox.search(query) return self.table.read()
class CreateDiscoveredReposView(View): """View which represent Discovered Repository section in Repository Discovery procedure. """ searchbox = Search() table = SatTable( locator=".//table", column_widgets={0: Checkbox(locator=".//input[@ng-change='itemSelected(urlRow)']")}, ) create_action = Text("//button[contains(., 'Create Selected')]") def fill(self, values): """Select necessary repo/repos to be added to new or existing product""" if not isinstance(values, list): values = list((values,)) for value in values: self.table.row(discovered_repository__contains=value)[0].fill(True) self.create_action.click() def read(self): return self.table.read()
class CloudInventoryListView(BaseLoggedInView): """Main RH Cloud Inventory Upload view.""" title = Text("//h1[@class='inventory_title']") searchbox = Search() allow_auto_upload = InventoryBootstrapSwitch( class_name='auto_upload_switcher') obfuscate_host_names = InventoryBootstrapSwitch( class_name='host_obfuscation_switcher') @View.nested class inventory_list(ItemsList): item_class = InventoryItem @property def widget_names(self): return [item.description for item in self.items()] def read(self): return {item.description: item.read() for item in self.items()} @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None
class DashboardView(BaseLoggedInView): title = Text("//h1[text()='Overview']") manage = ActionsDropdown("//div[@class='btn-group']") refresh = AutoRefresh() searchbox = Search() @property def is_displayed(self): return self.browser.wait_for_element(self.title, exception=False) is not None def search(self, query): """Return whole dashboard view as a result of a search :param str query: search query to type into search field. :return: all view widgets values :rtype: dict """ self.searchbox.search(query) return self.read() @View.nested class DiscoveredHosts(View): ROOT = ".//li[@data-name='Discovered Hosts']" hosts = Table('.//table') hosts_count = Text(".//a[@data-id='aid_discovered_hosts']") @View.nested class HostConfigurationStatus(View): ROOT = ".//li[@data-name='Host Configuration Status for All']" status_list = ItemValueList() total_count = TotalCount() @View.nested class TaskStatus(View): ROOT = ".//li[@data-name='Task Status']" states = SatTable( './/table', column_widgets={'No. of Tasks': Text('./a')}, ) def fill(self, values): if 'state' not in values or 'result' not in values: raise ValueError( 'both state and result values have to be provided') self.states.row( state=values['state'], result=values['result'])['No. of Tasks'].widget.click() @View.nested class HostConfigurationChart(View): ROOT = ".//li[@data-name='Host Configuration Chart for All']" chart = PieChart(".//div[@id='host_configuration_chart_all']") @View.nested class ContentViews(View): ROOT = ".//li[@data-name='Content Views']" content_views = SatTable('.//table', column_widgets={'Content View': Text('./a')}) @View.nested class SyncOverview(View): ROOT = ".//li[@data-name='Sync Overview']" syncs = Table('.//table') @View.nested class HostSubscription(View): ROOT = ".//li[@data-name='Host Subscription Status']" subscriptions = SatTable('.//table', column_widgets={0: Text('./a')}) def fill(self, values): if 'type' not in values: raise ValueError('You need provide subscription task type') self.subscriptions.row( (0, 'contains', '{}'.format(values['type'])))[0].widget.click() @View.nested class SubscriptionStatus(View): ROOT = ".//li[@data-name='Subscription Status']" subscriptions = SatTable('.//table') @View.nested class LatestErrata(View): ROOT = ".//li[@data-name='Latest Errata']" erratas = Table('.//table') @View.nested class NewHosts(View): ROOT = ".//li[@data-name='New Hosts']" hosts = Table('.//table') @View.nested class HostCollections(View): ROOT = ".//li[@data-name='Host Collections']" collections = SatTable('.//table') @View.nested class LatestFailedTasks(View): ROOT = ".//li[@data-name='Latest Warning/Error Tasks']" tasks = SatTable('.//table', column_widgets={'Name': Text('./a')}) def fill(self, values): if 'name' not in values: raise ValueError('You need provide name of the task') self.tasks.row(name=values['name'])['Name'].widget.click() @View.nested class VirtWhoConfigStatus(View): ROOT = ".//li[@data-name='Virt-who Configs Status']" config_status = Table('.//table') latest_config = Text(".//div[@class='ca']")