diff --git a/airgun/entities/contentview_new.py b/airgun/entities/contentview_new.py index 6488776ae..f73fd3ca8 100644 --- a/airgun/entities/contentview_new.py +++ b/airgun/entities/contentview_new.py @@ -7,11 +7,13 @@ from airgun.navigation import NavigateStep, navigator from airgun.utils import retry_navigation from airgun.views.contentview_new import ( + AddContentViewModal, AddRPMRuleView, ContentViewCreateView, ContentViewEditView, ContentViewTableView, ContentViewVersionDetailsView, + ContentViewVersionPromoteView, ContentViewVersionPublishView, CreateFilterView, EditFilterView, @@ -21,11 +23,13 @@ class NewContentViewEntity(BaseEntity): endpoint_path = '/content_views' - def create(self, values): + def create(self, values, composite=False): """Create a new content view""" view = self.navigate_to(self, 'New') self.browser.plugin.ensure_page_safe(timeout='5s') view.wait_displayed() + if composite: + view.composite_tile.click() view.fill(values) view.submit.click() @@ -36,20 +40,57 @@ def search(self, value): view.wait_displayed() return view.search(value) - def publish(self, entity_name, values=None): - """Publishes new version of CV""" + def publish(self, entity_name, values=None, promote=False, lce=None): + """Publishes new version of CV, optionally allowing for instant promotion""" view = self.navigate_to(self, 'Publish', entity_name=entity_name) self.browser.plugin.ensure_page_safe(timeout='5s') view.wait_displayed() if values: view.fill(values) - view.next.click() - view.finish.click() + if promote: + view.promote.click() + view.lce_selector.fill({lce: True}) + view.next_button.click() + view.finish_button.click() view = self.navigate_to(self, 'Edit', entity_name=entity_name) self.browser.plugin.ensure_page_safe(timeout='5s') view.wait_displayed() return view.versions.table.read() + def add_content(self, entity_name, content_name): + """Add specified content to the given Content View""" + view = self.navigate_to(self, 'Edit', entity_name=entity_name) + self.browser.plugin.ensure_page_safe(timeout='5s') + view.wait_displayed() + view.repositories.resources.add(content_name) + return view.repositories.resources.read() + + def add_cv(self, ccv_name, cv_name, always_update=False, version=None): + """Adds selected CV to selected CCV, optionally with support for always_update and specified version""" + view = self.navigate_to(self, 'Edit', entity_name=ccv_name) + self.browser.plugin.ensure_page_safe(timeout='5s') + view.wait_displayed() + view.content_views.resources.add(cv_name) + view = AddContentViewModal(self.browser) + if always_update: + view.always_update.fill(True) + if version: + view.version_select.item_select(version) + view.submit_button.click() + view = self.navigate_to(self, 'Edit', entity_name=ccv_name) + return view.content_views.resources.read() + + def read_cv(self, entity_name, version_name): + """Reads the table for a specified Content View's specified Version""" + view = self.navigate_to(self, 'Edit', entity_name=entity_name) + view.versions.search(version_name) + return view.versions.table.row(version=version_name).read() + + def read_repositories(self, entity_name): + """Reads the repositories table for a specified Content View""" + view = self.navigate_to(self, 'Edit', entity_name=entity_name) + return view.repositories.resources.read() + def read_version_table(self, entity_name, version, tab_name, search_param=None): """Reads a specific table for a CV Version""" view = self.navigate_to(self, 'Version', entity_name=entity_name, version=version) @@ -131,46 +172,20 @@ def read_french_lang_cv(self): view.wait_displayed() return view.table.read() - def publish(self, entity_name, values=None): - """Publishes to create new version of CV and promotes the contents to - 'Library' environment. - :return: dict with new content view version table row; contains keys - like 'Version', 'Status', 'Environments' etc. - """ - view = self.navigate_to(self, 'Publish', entity_name=entity_name) - if values: - view.fill(values) - view.next.click() - view.finish.click() - view.progressbar.wait_for_result() - view = self.navigate_to(self, 'Edit', entity_name=entity_name) - return view.versions.table.read() - def promote(self, entity_name, version_name, lce_name): """Promotes the selected version of content view to given environment. :return: dict with new content view version table row; contains keys like 'Version', 'Status', 'Environments' etc. """ view = self.navigate_to(self, 'Promote', entity_name=entity_name, version_name=version_name) - modal = NewContentViewVersionPromote(self.browser) + modal = ContentViewVersionPromoteView(self.browser) if modal.is_displayed: modal.lce.fill({lce_name: True}) - modal.promote.click() + modal.promote_btn.click() view = self.navigate_to(self, 'Edit', entity_name=entity_name) view.versions.search(version_name) return view.versions.table.row(version=version_name).read() - def publish_and_promote(self, entity_name, lce_name, values=None): - view = self.navigate_to(self, 'Publish', entity_name=entity_name) - if values: - view.fill(values) - view.lce(lce_name).fill(True) - view.next.click() - view.next.click() - view.progressbar.wait_for_result() - view = self.navigate_to(self, 'Edit', entity_name=entity_name) - return view.versions.table.read() - def update(self, entity_name, values): """Update existing content view""" view = self.navigate_to(self, 'Edit', entity_name=entity_name) @@ -205,24 +220,6 @@ def step(self, *args, **kwargs): self.view.menu.select('Contenu', 'Lifecycle', 'Content Views') -@navigator.register(NewContentViewEntity, 'Edit') -class EditContentView(NavigateStep): - """Navigate to Edit Content View screen. - Args: - entity_name: name of content view - """ - - VIEW = NewContentViewEditView - - def prerequisite(self, *args, **kwargs): - return self.navigate_to(self.obj, 'All') - - def step(self, *args, **kwargs): - entity_name = kwargs.get('entity_name') - self.parent.search(entity_name) - self.parent.table.row(name=entity_name)['Name'].widget.click() - - @navigator.register(NewContentViewEntity, 'New') class CreateContentView(NavigateStep): """Navigate to Create content view.""" @@ -235,22 +232,6 @@ def step(self, *args, **kwargs): self.parent.create_content_view.click() -@navigator.register(NewContentViewEntity, 'Publish') -class PublishContentViewVersion(NavigateStep): - """Navigate to Content View Publish screen.""" - - VIEW = ContentViewVersionPublishView - - def prerequisite(self, *args, **kwargs): - """Open Content View first.""" - return self.navigate_to(self.obj, 'Edit', entity_name=kwargs.get('entity_name')) - - @retry_navigation - def step(self, *args, **kwargs): - """Click 'Publish new version' button""" - self.parent.publish.click() - - @navigator.register(NewContentViewEntity, 'Edit') class EditContentView(NavigateStep): """Navigate to Edit Content View screen.""" @@ -288,7 +269,7 @@ class PublishContentViewVersion(NavigateStep): entity_name: name of content view """ - VIEW = NewContentViewVersionPublishView + VIEW = ContentViewVersionPublishView def prerequisite(self, *args, **kwargs): """Open Content View first.""" @@ -307,7 +288,7 @@ class PromoteContentViewVersion(NavigateStep): version_name: name of content view version to promote """ - VIEW = NewContentViewEditView + VIEW = ContentViewEditView def prerequisite(self, *args, **kwargs): return self.navigate_to(self.obj, 'Edit', entity_name=kwargs.get('entity_name')) diff --git a/airgun/views/common.py b/airgun/views/common.py index 5af42881d..a80b62bbc 100644 --- a/airgun/views/common.py +++ b/airgun/views/common.py @@ -1,7 +1,3 @@ -import time - -import wait_for -from wait_for import wait_for from widgetastic.widget import ( Checkbox, ConditionalSwitchableView, @@ -13,9 +9,10 @@ WTMixin, do_not_read_this_widget, ) -from widgetastic_patternfly import BreadCrumb, Button, Tab, TabWithDropdown +from widgetastic_patternfly import BreadCrumb, Tab, TabWithDropdown +from widgetastic_patternfly4 import Button, Select from widgetastic_patternfly4.navigation import Navigation -from widgetastic_patternfly4.ouia import Button as PF4Button, Dropdown, PatternflyTable +from widgetastic_patternfly4.ouia import Dropdown, PatternflyTable from airgun.utils import get_widget_by_name, normalize_dict_values from airgun.widgets import ( @@ -26,6 +23,7 @@ ItemsList, LCESelector, Pf4ConfirmationDialog, + PF4LCECheckSelector, PF4LCESelector, PF4NavSearch, PF4Search, @@ -286,6 +284,30 @@ class PF4LCESelectorGroup(LCESelectorGroup): ) +class PF4LCECheckSelectorGroup(PF4LCESelectorGroup): + """Checkbox version of PF4 LCE Selector""" + + lce = PF4LCECheckSelector( + locator=ParametrizedLocator( + './/div[@class="env-path" and .//*[contains(normalize-space(.), "{lce_name}")]]' + ) + ) + + +class PF4LCEGroup(ParametrizedLocator): + "Group of LCE indicators" + ROOT = './/td and ' + + PARAMETERS = ('lce_name',) + + LAST_ENV = './/div[@class="env-path"][last()]' + lce = PF4LCESelector( + locator=ParametrizedLocator( + './/div[@class="env-path" and .//*[contains(normalize-space(.), "{lce_name}")]]' + ) + ) + + class ListRemoveTab(SatSecondaryTab): """'List/Remove' tab, part of :class:`AddRemoveResourcesView`.""" @@ -393,17 +415,11 @@ def read(self): class NewAddRemoveResourcesView(View): searchbox = PF4Search() - type = Dropdown( - locator='.//div[contains(@class, "All repositories") or' - ' contains(@aria-haspopup="listbox")]' - ) - Status = Dropdown( - locator='.//div[contains(@class, "All") or contains(@aria-haspopup="listbox")]' - ) - add_repo = PF4Button('OUIA-Generated-Button-secondary-2') - # Need to add kebab menu + status = Select(locator='.//div[@data-ouia-component-id="select Status"]') + remove_button = Dropdown(locator='.//div[@data-ouia-component-id="repositoies-bulk-actions"]') + add_button = Button(locator='.//button[@data-ouia-component-id="add-repositories"]') table = PatternflyTable( - component_id='OUIA-Generated-Table-4', + component_id='content-view-repositories-table', column_widgets={ 0: Checkbox(locator='.//input[@type="checkbox"]'), 'Type': Text('.//a'), @@ -415,30 +431,25 @@ class NewAddRemoveResourcesView(View): }, ) + def select_status(self, value): + """Set status box to passed in value""" + self.status.fill(value) + def search(self, value): """Search for specific available resource and return the results""" self.searchbox.search(value) - # Tried following ways to wait for table to be displayed, only sleep worked - # Might need a before/after fill - wait_for( - lambda: self.table.is_displayed is True, - timeout=60, - delay=1, - ) - time.sleep(3) - self.table.wait_displayed() return self.table.read() def add(self, value): """Associate specific resource""" + self.select_status("Not added") self.search(value) + value = self.table.rows() next(self.table.rows())[0].widget.fill(True) - self.add_repo.click() + 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) @@ -447,12 +458,14 @@ def remove(self, value): :param str or list values: string containing resource name or a list of such strings. """ + self.select_status("Added") self.search(value) next(self.table.rows())[0].widget.fill(True) - self.remove_button.click() + self.remove_button.item_select('Remove') def read(self): """Read all table values from both resource tables""" + self.select_status("All") return self.table.read() @@ -475,73 +488,6 @@ class add_tab(AddTab): ) -class NewAddRemoveResourcesView(View): - searchbox = PF4Search() - type = Dropdown( - locator='.//div[contains(@class, "All repositories") or' - ' contains(@aria-haspopup="listbox")]' - ) - Status = Dropdown( - locator='.//div[contains(@class, "All") or contains(@aria-haspopup="listbox")]' - ) - add_repo = PF4Button('OUIA-Generated-Button-secondary-2') - # Need to add kebab menu - table = PatternflyTable( - component_id='OUIA-Generated-Table-4', - column_widgets={ - 0: Checkbox(locator='.//input[@type="checkbox"]'), - 'Type': Text('.//a'), - 'Name': Text('.//a'), - 'Product': Text('.//a'), - 'Sync State': Text('.//a'), - 'Content': Text('.//a'), - 'Status': Text('.//a'), - }, - ) - - def search(self, value): - """Search for specific available resource and return the results""" - self.searchbox.search(value) - # Tried following ways to wait for table to be displayed, only sleep worked - # Might need a before/after fill - wait_for( - lambda: self.table.is_displayed is True, - timeout=60, - delay=1, - ) - time.sleep(3) - self.table.wait_displayed() - return self.table.read() - - def add(self, value): - """Associate specific resource""" - self.search(value) - next(self.table.rows())[0].widget.fill(True) - self.add_repo.click() - - def fill(self, values): - """Associate resource(s)""" - if not isinstance(values, list): - values = [ - values, - ] - for value in values: - self.add(value) - - def remove(self, value): - """Unassign some resource(s). - :param str or list values: string containing resource name or a list of - such strings. - """ - self.search(value) - next(self.table.rows())[0].widget.fill(True) - self.remove_button.click() - - def read(self): - """Read all table values from both resource tables""" - return self.table.read() - - class TemplateEditor(View): """Default view for template entity editor that can be present for example on provisioning template of partition table pages. It contains from diff --git a/airgun/views/contentview_new.py b/airgun/views/contentview_new.py index d2d71f7f6..12c955f2c 100644 --- a/airgun/views/contentview_new.py +++ b/airgun/views/contentview_new.py @@ -1,8 +1,8 @@ from wait_for import wait_for from widgetastic.utils import ParametrizedLocator -from widgetastic.widget import Checkbox, Text, TextInput, View +from widgetastic.widget import Checkbox, ParametrizedView, Text, TextInput, View from widgetastic_patternfly import BreadCrumb, Tab -from widgetastic_patternfly4 import Button, Dropdown, Radio as PF4Radio +from widgetastic_patternfly4 import Button, Dropdown, Modal, Radio as PF4Radio, Select from widgetastic_patternfly4.ouia import ( Button as PF4Button, ExpandableTable, @@ -14,6 +14,8 @@ from airgun.views.common import ( BaseLoggedInView, NewAddRemoveResourcesView, + PF4LCECheckSelectorGroup, + PF4LCESelectorGroup, SearchableViewMixinPF4, ) from airgun.widgets import ( @@ -28,66 +30,36 @@ LOCATION_NUM = 3 -class NewAddRemoveResourcesView(View): - searchbox = PF4Search() - type = Dropdown( - locator='.//div[contains(@class, "All repositories") or' - ' contains(@aria-haspopup="listbox")]' - ) - Status = Dropdown( - locator='.//div[contains(@class, "All") or contains(@aria-haspopup="listbox")]' - ) - add_repo = PF4Button('OUIA-Generated-Button-secondary-2') - # Need to add kebab menu +class ContentViewAddResourcesView(NewAddRemoveResourcesView): + remove_button = Dropdown(locator='.//div[@data-ouia-component-id="cv-components-bulk-actions"]') + add_button = Button(locator='.//button[@data-ouia-component-id="add-content-views"]') table = PatternflyTable( - component_id='OUIA-Generated-Table-4', + component_id='content-view-components-table', column_widgets={ 0: Checkbox(locator='.//input[@type="checkbox"]'), 'Type': Text('.//a'), 'Name': Text('.//a'), - 'Product': Text('.//a'), - 'Sync State': Text('.//a'), - 'Content': Text('.//a'), + 'Version': Text('.//a'), + 'Environments': Text('.//td[5]'), + 'Repositories': Text('.//a'), 'Status': Text('.//a'), + 'Description': Text('.//a'), + 8: Dropdown(locator='.//div[contains(@class, "pf-c-dropdown")]'), }, ) - def search(self, value): - """Search for specific available resource and return the results""" - self.searchbox.search(value) - wait_for( - lambda: self.table.is_displayed is True, - timeout=60, - delay=1, - ) - self.table.wait_displayed() - return self.table.read() - - def add(self, value): - """Associate specific resource""" - self.search(value) - next(self.table.rows())[0].widget.fill(True) - self.add_repo.click() - - def fill(self, values): - """Associate resource(s)""" - if not isinstance(values, list): - values = [values] - for value in values: - self.add(value) - - def remove(self, value): - """Unassign some resource(s). - :param str or list values: string containing resource name or a list of - such strings. - """ - self.search(value) - next(self.table.rows())[0].widget.fill(True) - self.remove_button.click() - def read(self): - """Read all table values from both resource tables""" - return self.table.read() +class AddContentViewModal(BaseLoggedInView): + title = Text('.//div[@data-ouia-component-id="add-content-views"]') + submit_button = PF4Button('add-components-modal-add') + cancel_button = PF4Button('add-components-modal-cancel') + + version_select = Select(locator=".//div[@data-ouia-component-id='add-content-views']") + always_update = Checkbox(locator=".//input[@class='pf-c-check__input']") + + @property + def is_displayed(self): + return self.title.is_displayed class ContentViewTableView(BaseLoggedInView, SearchableViewMixinPF4): @@ -117,22 +89,11 @@ class ContentViewCreateView(BaseLoggedInView): submit = PF4Button('create-content-view-form-submit') cancel = PF4Button('create-content-view-form-cancel') - @View.nested - class component(View): - component_tile = Text('//div[contains(@id, "component")]') - solve_dependencies = Checkbox(id='dependencies') - import_only = Checkbox(id='importOnly') - - def child_widget_accessed(self, widget): - self.component_tile.click() - - @View.nested - class composite(View): - composite_tile = Text('//div[contains(@id, "composite")]') - auto_publish = Checkbox(id='autoPublish') - - def child_widget_accessed(self, widget): - self.composite_tile.click() + component_tile = Text('//div[contains(@id, "component")]') + solve_dependencies = Checkbox(id='dependencies') + import_only = Checkbox(id='importOnly') + composite_tile = Text('//div[contains(@id, "composite")]') + auto_publish = Checkbox(id='autoPublish') @property def is_displayed(self): @@ -177,7 +138,7 @@ class versions(Tab): column_widgets={ 0: Checkbox(locator='.//input[@type="checkbox"]'), 'Version': Text('.//a'), - 'Environments': Text('.//a'), + 'Environments': Text('.//td[3]'), 'Packages': Text('.//a'), 'Errata': Text('.//a'), 'Additional content': Text('.//a'), @@ -203,7 +164,7 @@ def search(self, version_name): class content_views(Tab): TAB_LOCATOR = ParametrizedLocator('//a[contains(@href, "#/contentviews")]') - resources = View.nested(NewAddRemoveResourcesView) + resources = View.nested(ContentViewAddResourcesView) @View.nested class repositories(Tab): @@ -242,14 +203,13 @@ class ContentViewVersionPublishView(BaseLoggedInView): description = TextInput(id='description') promote = Switch('promote-switch') - # review screen only has info to review - # shared buttons at bottom for popup for both push and review section - next = Button('Next') - finish = Button('Finish') - back = Button('Back') - cancel = Button('Cancel') + next_button = Button('Next') + finish_button = Button('Finish') + back_button = Button('Back') + cancel_button = Button('Cancel') close_button = Button('Close') progressbar = PF4ProgressBar('.//div[contains(@class, "pf-c-wizard__main-body")]') + lce_selector = ParametrizedView.nested(PF4LCECheckSelectorGroup) @property def is_displayed(self): @@ -276,6 +236,15 @@ def before_fill(self, values=None): ) +class ContentViewVersionPromoteView(Modal): + ROOT = './/div[@data-ouia-component-id="promote-version"]' + + description = Text('.//h2[@data-ouia-component-id="description-text-value"]') + lce_selector = ParametrizedView.nested(PF4LCESelectorGroup) + promote_btn = Button(locator='//button[normalize-space(.)="Promote"]') + cancel_btn = Button(locator='//button[normalize-space(.)="Cancel"]') + + class ContentViewVersionDetailsView(BaseLoggedInView): breadcrumb = BreadCrumb() version = Text(locator='.//h2[@data-ouia-component-id="cv-version"]') diff --git a/airgun/widgets.py b/airgun/widgets.py index b78e884f4..308253a61 100644 --- a/airgun/widgets.py +++ b/airgun/widgets.py @@ -1511,6 +1511,13 @@ def checkbox_selected(self, locator): return self.browser.is_selected(locator) +class PF4LCECheckSelector(PF4LCESelector): + """Checkbox version of PF4 LCE Selector""" + + LABELS = './/label[contains(@class, "pf-c-check__label")]' + CHECKBOX = './/input[contains(@class, "pf-c-check") and ../label[.//*[contains(text(), "{}")]]]' + + class LimitInput(Widget): """Input for managing limits (e.g. Hosts limit). Consists of 'Unlimited' checkbox and text input for specifying the limit, which is only visible if