class InfrastructureMappingView(BaseLoggedInPage): """This is an entire separate page, with many elements similar to what we had in MigrationPlanRequestDetailsView , so re-using some of those Paginator things by renaming those from v2vPaginator to v2vPaginator, etc.""" infra_mapping_list = InfraMappingList("infra-mappings-list-view") create_infrastructure_mapping = Text( locator='(//a|//button)' '[text()="Create Infrastructure Mapping"]') configure_providers = Text(locator='//a[text()="Configure Providers"]') paginator_view = View.include(v2vPaginatorPane, use_parent=True) search_box = TextInput( locator=".//div[contains(@class,'input-group')]/input") clear_filters = Text(".//a[text()='Clear All Filters']") # Used for Ascending/Descending sort sort_order = Text(".//button[./span[contains(@class,'sort-direction')]]") # Used to select filter_by 'Name' or 'Description' filter_by_dropdown = SelectorDropdown('id', 'filterFieldTypeMenu') # USed to select sort by options like 'Name', 'Number of Associated Plans' sort_by_dropdown = SelectorDropdown('id', 'sortTypeMenu') @property def is_displayed(self): # TODO: Resmove 1st condition, once /manageiq-v2v/issues/768 fix is backported to 510z return ( (self.navigation.currently_selected == ['Compute', 'Migration', 'Overview'] or self.navigation.currently_selected == ['Compute', 'Migration', 'Infrastructure Mappings']) and len( self.browser.elements(".//div[contains(@class,'spinner')]")) == 0 and (self.create_infrastructure_mapping.is_displayed or self.infra_mapping_list.is_displayed or self.configure_providers.is_displayed))
class advanced(View): # noqa pre_playbook = BootstrapSelect('preMigrationPlaybook') post_playbook = BootstrapSelect('postMigrationPlaybook') pre_checkbox = Text( locator='.//input[contains(@id, "pre_migration_select_all")]') post_checkbox = Text( locator='.//input[contains(@id, "post_migration_select_all")]')
class MigrationDashboardView(BaseLoggedInPage): create_infrastructure_mapping = Text( locator='(//a|//button)' '[text()="Create Infrastructure Mapping"]') create_migration_plan = Text( locator='(//a|//button)[text()="Create Migration Plan"]') configure_providers = Text(locator='//a[text()="Configure Providers"]') migration_plans_not_started_list = MigrationPlansList( "plans-not-started-list") migration_plans_completed_list = MigrationPlansList("plans-complete-list") migration_plans_archived_list = MigrationPlansList("plans-complete-list") infra_mapping_list = InfraMappingList("infra-mappings-list-view") migr_dropdown = MigrationDropdown(text="Not Started Plans") # TODO: XPATH requested to devel (repo:miq_v2v_ui_plugin issues:415) progress_card = MigrationProgressBar( locator='.//div[3]/div/div[3]/div[3]/div/div') @property def is_displayed(self): return ( self.navigation.currently_selected == ['Compute', 'Migration'] and (len(self.browser.elements(".//div[contains(@class,'spinner')]")) == 0) and (len(self.browser.elements('.//div[contains(@class,"card-pf")]')) > 0)) def plan_in_progress(self, plan_name): """MIQ V2V UI is going through redesign as OSP will be integrated. # TODO: This also means that mappings/plans may be moved to different pages. Once all of that is settled we will need to refactor and also account for notifications. """ try: try: is_plan_visible = self.progress_card.is_plan_visible(plan_name) plan_time_elapsed = self.progress_card.get_clock(plan_name) except ItemNotFound: # This will end the wait_for loop and check the plan under completed_plans section return True if is_plan_visible: # log current status # uncomment following logs after @Yadnyawalk updates the widget for in progress card # logger.info("For plan %s, current migrated size is %s out of total size %s", # migration_plan.name, view.progress_card.get_migrated_size(plan_name), # view.progress_card.get_total_size(migration_plan.name)) # logger.info("For plan %s, current migrated VMs are %s out of total VMs %s", # migration_plan.name, view.progress_card.migrated_vms(migration_plan.name), # view.progress_card.total_vm_to_be_migrated(migration_plan.name)) self.logger.info( "For plan %s, is plan in progress: %s," "time elapsed for migration: %s", plan_name, is_plan_visible, plan_time_elapsed) # return False if plan visible under "In Progress Plans" return not is_plan_visible except StaleElementReferenceException: self.browser.refresh() self.migr_dropdown.item_select("In Progress Plans") return False
class vms(View): # noqa import_btn = Button('Import') importcsv = Button('Import CSV') hidden_field = HiddenFileInput(locator='.//input[contains(@accept,".csv")]') # TODO: Replace string keys by integer keys after row indexing issue get fixed # TODO: Replace Text by Button or GenericLocatorWidget after button text get added table = Table('.//div[contains(@class, "container-fluid")]/table', column_widgets={ 'Select': Checkbox(locator=".//input"), 1: Text('.//span/button[contains(@class,"btn btn-link")]')}) filter_by_dropdown = SelectorDropdown('id', 'filterFieldTypeMenu') search_box = TextInput(locator='.//div[contains(@class, "modal-content")]' '//div[contains(@class,"input-group")]/input') clear_filters = Text(".//a[text()='Clear All Filters']") error_text = Text('.//h3[contains(@class,"blank-slate-pf-main-action") and ' 'contains(text(), "Error:")]') error_icon = Text(locator='.//span[contains(@class, "pficon-error-circle-o")]') popover_text = Text(locator='.//div[contains(@class, "popover-content")]') @property def is_displayed(self): return (self.table.is_displayed and (len(self.browser.elements(".//div[contains(@class,'spinner')]")) == 0)) def filter_by_name(self, vm_name): try: # remove `if` cond https://github.com/ManageIQ/manageiq-v2v/issues/771 fixed if self.browser.appliance.version < '5.10': # Don't remove nxt line, remove `if` self.filter_by_dropdown.item_select("VM Name") # just unindent except NoSuchElementException: self.logger.info("`VM Name` not present in filter dropdown!") self.search_box.fill(vm_name) self.browser.send_keys(Keys.ENTER, self.search_box) def filter_by_source_cluster(self, cluster_name): try: self.filter_by_dropdown.item_select("Source Cluster") except NoSuchElementException: self.logger.info("`Source Cluster` not present in filter dropdown!") self.search_box.fill(cluster_name) self.browser.send_keys(Keys.ENTER, self.search_box) def filter_by_path(self, path): try: self.filter_by_dropdown.item_select("Path") except NoSuchElementException: self.logger.info("`Path` not present in filter dropdown!") self.search_box.fill(path) self.browser.send_keys(Keys.ENTER, self.search_box) def select_by_name(self, vm_name): self.filter_by_name(vm_name) vms_selected = [] for row in self.table.rows(): if vm_name in row.read()['VM Name']: row.select.fill(True) vms_selected.append(row.read()['VM Name']) return vms_selected
class MigrationDashboardView(BaseLoggedInPage): create_infrastructure_mapping = Text( locator='(//a|//button)' '[text()="Create Infrastructure Mapping"]') create_migration_plan = Text( locator='(//a|//button)[text()="Create Migration Plan"]') @property def is_displayed(self): return self.navigation.currently_selected == ['Compute', 'Migration']
class general(InfraMappingCommonButtons): # noqa name = TextInput(name="name") description = TextInput(name="description") plan_type = BootstrapSelect("targetProvider") name_help_text = Text(locator='.//div[contains(@id,"name")]/span') description_help_text = Text(locator='.//div[contains(@id,"description")]/span') fill_strategy = WaitFillViewStrategy() def after_fill(self, was_change): if was_change: self.next_btn.click()
class MigrationDashboardView(BaseLoggedInPage): create_infrastructure_mapping = Text( locator='(//a|//button)' '[text()="Create Infrastructure Mapping"]') create_migration_plan = Text( locator='(//a|//button)[text()="Create Migration Plan"]') configure_providers = Text(locator='//a[text()="Configure Providers"]') migration_plans_not_started_list = MigrationPlansList( "plans-not-started-list") migration_plans_completed_list = MigrationPlansList("plans-complete-list") infra_mapping_list = InfraMappingList("infra-mappings-list-view") migr_dropdown = MigrationDropdown(text="Not Started Plans") # TODO: XPATH requested to devel (repo:miq_v2v_ui_plugin issues:415) progress_bar = MigrationProgressBar( locator='.//div[3]/div/div[3]/div[3]/div/div') def _get_status(self, plan_name): """Returns status of migration plan""" # TODO: Error handling with try-except not_started = "Not Started Plans" self.migr_dropdown.item_select(not_started) if not self.migration_plans_not_started_list.is_plan_completed( plan_name): in_progress = "In Progress Plans" self.migr_dropdown.item_select(in_progress) wait_for( lambda: bool(self.progress_bar.is_plan_started(plan_name)), message="migration plan is starting, be patient please", delay=5, num_sec=3600) if self.progress_bar.is_plan_started(plan_name): flag = in_progress else: completed = "Completed Plans" self.migr_dropdown.item_select(completed) if self.migration_plans_completed_list.is_plan_completed( plan_name): flag = completed # TODO: Add archived plans tab to kk's widget else: flag = not_started self.migr_dropdown.item_select(flag) return flag @property def is_displayed(self): return ( self.navigation.currently_selected == ['Compute', 'Migration'] and (len(self.browser.elements(".//div[contains(@class,'spinner')]")) == 0) and (len(self.browser.elements('.//div[contains(@class,"card-pf")]')) > 0))
class InfraMappingWizardGeneralView(InfraMappingForm): name = TextInput(name='name') description = TextInput(name='description') name_help_text = Text(locator='.//div[contains(@id,"name")]/span') description_help_text = Text(locator='.//div[contains(@id,"description")]/span') def after_fill(self, was_change): if was_change: self.next_btn.click() @property def is_displayed(self): return self.name.is_displayed and self.description.is_displayed
class MigrationDashboardView(BaseLoggedInPage): create_infrastructure_mapping = Text( locator='(//a|//button)' '[text()="Create Infrastructure Mapping"]') create_migration_plan = Text( locator='(//a|//button)[text()="Create Migration Plan"]') migration_plans_not_started_list = MigrationPlansList( "plans-not-started-list") migration_plans_completed_list = MigrationPlansList("plans-complete-list") infra_mapping_list = InfraMappingList("infra-mappings-list-view") @property def is_displayed(self): return self.navigation.currently_selected == ['Compute', 'Migration']
class general(View): # noqa infra_map = BootstrapSelect("infrastructure_mapping") name = TextInput(name="name") name_help_text = Text(locator='.//div[contains(@id,"name")]/span') description = TextInput(name="description") choose_vm = RadioGroup('.//div[contains(@id,"vm_choice_radio")]') alert = Text('.//div[contains(@class, "alert")]') fill_strategy = WaitFillViewStrategy("15s") @property def is_displayed(self): return self.infra_map.is_displayed and self.name.is_displayed def after_fill(self, was_change): self.parent.next_btn.click()
class InfraMappingWizard(View): """Infrastructure Mapping Wizard Modal Widget. Usage: fill: takes values of following format: { 'general': { 'name':'infra_map_{}'.format(fauxfactory.gen_alphanumeric()), 'description':fauxfactory.gen_string("alphanumeric",length=50) }, 'cluster': { 'mappings': [ { 'sources':['Datacenter \ Cluster'], 'target':['Default \ Default'] } ] }, 'datastore':{ 'Cluster (Default)': { 'mappings':[ { 'sources':['NFS_Datastore_1','iSCSI_Datastore_1'], 'target':['hosted_storage'] }, { 'sources':['h02-Local_Datastore-8GB', 'h01-Local_Datastore-8GB'], 'target':['env-rhv41-01-nfs-iso'] } ] } }, 'network':{ 'Cluster (Default)': { 'mappings': [ { 'sources':['VM Network','VMkernel'], 'target':['ovirtmgmt'] }, { 'sources':['DPortGroup'], 'target':['Storage VLAN 33'] } ] } } } """ title = Text(locator='.//h4[contains(@class,"modal-title")]') general = View.nested(InfraMappingWizardGeneralView) cluster = View.nested(InfraMappingWizardClustersView) datastore = View.nested(InfraMappingWizardDatastoresView) network = View.nested(InfraMappingWizardNetworksView) result = View.nested(InfraMappingWizardResultsView) def after_fill(self, was_change): if was_change: self.result.close_btn.click()
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"
class general(InfraMappingCommonButtons): # noqa name = TextInput(name="name") description = TextInput(name="description") plan_type = BootstrapSelect("targetProvider") name_help_text = Text(locator='.//div[contains(@id,"name")]/span') description_help_text = Text( locator='.//div[contains(@id,"description")]/span') @View.nested class flash(V2VFlashMessages): ROOT = './/div[@class="modal-wizard-alert"]' fill_strategy = WaitFillViewStrategy() def after_fill(self, was_change): if was_change: self.next_btn.click()
class conversion_hosts(WaitTab): # noqa fill_strategy = WaitFillViewStrategy("20s") TAB_NAME = 'Conversion Hosts' title = Text('.//div[contains(@class, "pull-left")]//h3') including_entities = View.include(ConversionHostForm, use_parent=True) @property def is_displayed(self): return self.title.text == "Configured Conversion Host"
class HardCodedIP(View): """View to represent Hard Coded IP addresses report in analysis report""" title = Text('.//h1/div[@class="main"]') # TODO(ghubale): Add table widget @property def is_displayed(self): return self.title.text == "Hard-coded IP Addresses"
class Issues(View): """View to represent issues in analysis report""" title = Text('.//h1/div[@class="main"]') # TODO(ghubale): Add table widget to read all table on page @property def is_displayed(self): return self.title.text == "Issues"
class migration_throttling(WaitTab): # noqa fill_strategy = WaitFillViewStrategy("15s") TAB_NAME = 'Migration Throttling' title = Text('.//div[contains(@class, "pull-left")]//h3') including_entities = View.include(MigrationThrottlingForm, use_parent=True) @property def is_displayed(self): return self.title.text == "Concurrent Migrations"
class InfrastructureMappingForm(InfrastructureMappingView): """All infrastructure mapping View""" title = Text(locator='.//h4[contains(@class,"modal-title")]') @property def is_displayed(self): return self.title.text == self.form_title_text @View.nested class general(InfraMappingCommonButtons): # noqa name = TextInput(name="name") description = TextInput(name="description") plan_type = BootstrapSelect("targetProvider") name_help_text = Text(locator='.//div[contains(@id,"name")]/span') description_help_text = Text( locator='.//div[contains(@id,"description")]/span') @View.nested class flash(V2VFlashMessages): ROOT = './/div[@class="modal-wizard-alert"]' fill_strategy = WaitFillViewStrategy() def after_fill(self, was_change): if was_change: self.next_btn.click() @View.nested class cluster(ComponentView): # noqa comp_name = "cluster" @View.nested class datastore(ComponentView): # noqa comp_name = "datastore" @View.nested class network(ComponentView): # noqa comp_name = "network" def after_fill(self, was_change): if self.create_btn.is_displayed: self.create_btn.click() elif self.save.is_displayed: self.save.click() @View.nested class result(View): # noqa close_btn = Button("Close") continue_to_plan_wizard = Button("Continue to the plan wizard") success_icon = Text( './/div[contains(@class,"wizard-pf-success-icon")]') def after_fill(self, was_change): if was_change: self.result.close_btn.click()
class general(View): # noqa infra_map = BootstrapSelect('infrastructure_mapping') name = TextInput(name='name') name_help_text = Text(locator='.//div[contains(@id,"name")]/span') description = TextInput(name='description') select_vm = RadioGroup('.//div[contains(@id,"vm_choice_radio")]') @property def is_displayed(self): return self.infra_map.is_displayed and self.name.is_displayed
class MigrationDashboardView(BaseLoggedInPage): create_infrastructure_mapping = Text( locator='(//a|//button)' '[text()="Create Infrastructure Mapping"]') create_migration_plan = Text( locator='(//a|//button)[text()="Create Migration Plan"]') migration_plans_not_started_list = MigrationPlansList( "plans-not-started-list") migration_plans_completed_list = MigrationPlansList("plans-complete-list") infra_mapping_list = InfraMappingList("infra-mappings-list-view") migr_dropdown = MigrationDropdown(text="Not Started Plans") @property def is_displayed(self): return ( self.navigation.currently_selected == ['Compute', 'Migration'] and (len(self.browser.elements(".//div[contains(@class,'spinner')]")) == 0) and (len(self.browser.elements('.//div[contains(@class,"card-pf")]')) > 0))
class AllInfraMappingView(InfrastructureMappingView): """All infrastructure mapping View""" infra_mapping_list = InfraMappingList("infra-mappings-list-view") create_infra_mapping = Text(locator='(//a|//button)[text()="Create Infrastructure Mapping"]') configure_providers = Text(locator='//a[text()="Configure Providers"]') paginator_view = View.include(V2VPaginatorPane, use_parent=True) search_box = TextInput(locator=".//div[contains(@class,'input-group')]/input") clear_filters = Text(".//a[text()='Clear All Filters']") sort_order = Text(".//button[./span[contains(@class,'sort-direction')]]") filter_by_dropdown = SelectorDropdown("id", "filterFieldTypeMenu") sort_by_dropdown = SelectorDropdown("id", "sortTypeMenu") @property def is_displayed(self): return len(self.browser.elements(".//div[contains(@class,'spinner')]")) == 0 and ( self.create_infra_mapping.is_displayed or self.infra_mapping_list.is_displayed or self.configure_providers.is_displayed )
class InfraMappingCommonButtons(InfrastructureMappingView): back_btn = Button("Back") next_btn = Button("Next") cancel_btn = Button("Cancel") save = Button("Save") create_btn = Button("Create") add_mapping = Button("Add Mapping") remove_mapping = Button("Remove Selected") remove_all_mappings = Button("Remove All") mappings_tree = InfraMappingTreeView(tree_class="treeview") alert = Text('.//div[contains(@class, "alert")]')
class instance_properties(View): # noqa table = Table('.//div[contains(@class, "container-fluid")]/table') edit_instance_properties = Text( locator='.//button/span[contains(@class, "pficon-edit")]') select_security_group = Select(locator='.//td[5]/select') select_flavor = Select(locator='.//td[6]/select') save_properties = Text( locator='.//div[contains(@class, "inline-edit-buttons")]' '/button[contains(@aria-label, "Save")]') @property def is_displayed(self): return (self.table.is_displayed and (len( self.browser.elements(".//div[contains(@class,'spinner')]")) == 0)) def fill(self, values): """ This is required only for OSP and only if we want to edit osp_security_group or osp_flavor otherwise not needed. If none them is to be edited only next needs to be clicked. Args: values: """ was_change = True osp_security_group = values.get('osp_security_group') osp_flavor = values.get('osp_flavor') if osp_security_group or osp_flavor: self.edit_instance_properties.click() self.select_security_group.wait_displayed() if osp_security_group: self.select_security_group.fill(osp_security_group) if osp_flavor: self.select_flavor.fill(osp_flavor) self.save_properties.click() self.after_fill(was_change) return was_change def after_fill(self, was_change): self.parent.next_btn.click()
class InfrastructureMappingView(BaseLoggedInPage): title = Text("#explorer_title_text") @property def in_mapping_explorer(self): return self.logged_in_as_current_user and ( self.navigation.currently_selected == ["Compute", "Migration", "Infrastructure Mappings"]) @property def is_displayed(self): return self.in_mapping_explorer
class AddMigrationPlanView(View): title = Text(locator='.//h4[contains(@class,"modal-title")]') name = TextInput(name='name') description = TextInput(name='description') back_btn = Button('Back') # Since next is a keyword, suffixing it with btn and other two # because want to keep it consistent next_btn = Button('Next') cancel_btn = Button('Cancel') @property def is_displayed(self): return self.title.text == 'Migration Plan Wizard'
class InfrastructureMappingView(BaseLoggedInPage): title = Text("#explorer_title_text") @property def in_mapping_explorer(self): nav_menu = (["Compute", "Migration", "Infrastructure Mappings"] if self.context["object"].appliance.version < "5.11" else ["Migration", "Infrastructure Mappings"]) return self.logged_in_as_current_user and ( self.navigation.currently_selected == nav_menu) @property def is_displayed(self): return self.in_mapping_explorer
class MigrationPlanView(MigrationView): dashboard_cards = View.nested(DashboardCards) paginator_view = View.include(V2VPaginatorPane, use_parent=True) title = Text('.//div[contains(@class, "pull-left")]//h3') create_migration_plan = Text(locator='(//a|//button)[text()="Create Migration Plan"]') configure_providers = Text(locator='//a[text()="Configure Providers"]') sort_type_dropdown = SelectorDropdown("id", "sortTypeMenu") sort_direction = Text(locator=".//span[contains(@class,'sort-direction')]") progress_card = MigrationProgressBar() search_box = SearchBox(locator=".//div[contains(@class,'input-group')]/input") clear_filters = Text(".//a[text()='Clear All Filters']") @property def in_migration_plan(self): return ( self.in_explorer and self.create_migration_plan.is_displayed and len(self.browser.elements('.//div[contains(@class,"card-pf")]')) > 0 and len(self.browser.elements(PFIcon.icons.WARNING)) < 1 ) @property def is_displayed(self): return self.in_migration_plan
class SSUIBaseLoggedInPage(View): """This page should be subclassed by any page that models any other page that is available as logged in. """ flash = View.nested(FlashMessages) # TODO don't use `help` here, its a built-in help = SSUIHelpNavDropdown() navigation = SSUIVerticalNavigation('//ul[@class="list-group"]') domain_switcher = Button(id="domain-switcher") shopping_cart = Text('.//li/a[@title="Shopping cart"]') settings = SSUISettingsNavDropdown() @property def is_displayed(self): return self.logged_in_as_current_user def logged_in_as_user(self, user): if self.logged_out: return False return user.name == self.current_fullname @property def logged_in_as_current_user(self): return self.logged_in_as_user(self.extra.appliance.user) # TODO remove this property, it is erroneous. View properties should be returning data from UI @property def current_username(self): try: return self.extra.appliance.user.principal except AttributeError: return None @property def current_fullname(self): return self.settings.text @property def logged_in(self): return (self.settings.is_displayed and self.shopping_cart.is_displayed) @property def logged_out(self): return not self.logged_in def logout(self): self.settings.select_item('Logout') self.browser.handle_alert(wait=None) self.extra.appliance.user = None
class vms(View): # noqa import_btn = Button('Import') importcsv = Button('Import CSV') hidden_field = HiddenFileInput( locator='.//input[contains(@accept,".csv")]') table = Table('.//div[contains(@class, "container-fluid")]/table', column_widgets={"Select": Checkbox(locator=".//input")}) filter_by_dropdown = SelectorDropdown('id', 'filterFieldTypeMenu') search_box = TextInput( locator=".//div[contains(@class,'input-group')]/input") clear_filters = Text(".//a[text()='Clear All Filters']") @property def is_displayed(self): return self.filter_by_dropdown.is_displayed def filter_by_name(self, vm_name): try: self.filter_by_dropdown.item_select("VM Name") except NoSuchElementException: self.logger.info("`VM Name` not present in filter dropdown!") self.search_box.fill(vm_name) self.browser.send_keys(Keys.ENTER, self.search_box) def filter_by_source_cluster(self, cluster_name): try: self.filter_by_dropdown.item_select("Source Cluster") except NoSuchElementException: self.logger.info( "`Source Cluster` not present in filter dropdown!") self.search_box.fill(cluster_name) self.browser.send_keys(Keys.ENTER, self.search_box) def filter_by_path(self, path): try: self.filter_by_dropdown.item_select("Path") except NoSuchElementException: self.logger.info("`Path` not present in filter dropdown!") self.search_box.fill(path) self.browser.send_keys(Keys.ENTER, self.search_box) def select_by_name(self, vm_name): self.filter_by_name(vm_name) vms_selected = [] for row in self.table.rows(): if vm_name in row.read()['VM Name']: row.select.fill(True) vms_selected.append(row.read()['VM Name']) return vms_selected
class MigrationDashboardView(BaseLoggedInPage): create_infrastructure_mapping = Text( locator='(//a|//button)' '[text()="Create Infrastructure Mapping"]') create_migration_plan = Text( locator='(//a|//button)[text()="Create Migration Plan"]') configure_providers = Text(locator='//a[text()="Configure Providers"]') migration_plans_not_started_list = MigrationPlansList( "plans-not-started-list") migration_plans_completed_list = MigrationPlansList("plans-complete-list") infra_mapping_list = InfraMappingList("infra-mappings-list-view") migr_dropdown = MigrationDropdown(text="Not Started Plans") # TODO: XPATH requested to devel (repo:miq_v2v_ui_plugin issues:415) progress_card = MigrationProgressBar( locator='.//div[3]/div/div[3]/div[3]/div/div') @property def is_displayed(self): return ( self.navigation.currently_selected == ['Compute', 'Migration'] and (len(self.browser.elements(".//div[contains(@class,'spinner')]")) == 0) and (len(self.browser.elements('.//div[contains(@class,"card-pf")]')) > 0))