# -*- coding: utf-8 -*-
from time import sleep
import os
from lxml.html import document_fromstring
from widgetastic.exceptions import NoSuchElementException
from widgetastic.widget import (
View, Text, TextInput, ParametrizedView, Image, ConditionalSwitchableView)
from widgetastic_patternfly import (
Dropdown, BootstrapSelect, Tab, Input, CheckableBootstrapTreeview)
from cfme.base.login import BaseLoggedInPage
from cfme.exceptions import TemplateNotFound
from widgetastic_manageiq import (Calendar,
Checkbox,
SummaryTable,
Button,
ItemsToolBarViewSelector,
Table,
MultiBoxSelect,
RadioGroup,
VersionPick,
Version,
BaseEntitiesView,
NonJSBaseEntity,
BaseListEntity,
BaseQuadIconEntity,
BaseTileIconEntity,
JSBaseEntity,
BaseNonInteractiveEntitiesView,
BreadCrumb,
PaginationPane,
DriftComparison)
[docs]class InstanceQuadIconEntity(BaseQuadIconEntity):
""" Provider child of Quad Icon entity
"""
@property
def data(self):
br = self.browser
try:
if br.product_version > '5.8':
state = br.get_attribute('style', self.QUADRANT.format(pos='b'))
state = state.split('"')[1]
else:
state = br.get_attribute('src', self.QUADRANT.format(pos='b'))
state = os.path.split(state)[1]
state = os.path.splitext(state)[0]
except NoSuchElementException:
return {}
except IndexError:
state = ''
if br.is_displayed(self.QUADRANT.format(pos='g')):
policy = br.get_attribute('src', self.QUADRANT.format(pos='g'))
else:
policy = None
return {
"os": br.get_attribute('src', self.QUADRANT.format(pos='a')),
"state": state,
"vendor": br.get_attribute('src', self.QUADRANT.format(pos='c')),
"no_snapshot": br.text(self.QUADRANT.format(pos='d')),
"policy": policy,
}
[docs]class InstanceTileIconEntity(BaseTileIconEntity):
""" Provider child of Tile Icon entity
"""
quad_icon = ParametrizedView.nested(InstanceQuadIconEntity)
[docs]class InstanceListEntity(BaseListEntity):
""" Provider child of List entity
"""
pass
[docs]class NonJSInstanceEntity(NonJSBaseEntity):
""" Provider child of Proxy entity
"""
quad_entity = InstanceQuadIconEntity
list_entity = InstanceListEntity
tile_entity = InstanceTileIconEntity
[docs]class JSInstanceEntity(JSBaseEntity):
@property
def data(self):
data_dict = super(JSInstanceEntity, self).data
if 'quadicon' in data_dict and data_dict['quadicon']:
try:
quad_data = document_fromstring(data_dict['quadicon'])
data_dict['os'] = quad_data.xpath(self.QUADRANT.format(pos="a"))[0].get('src')
data_dict['vendor'] = quad_data.xpath(self.QUADRANT.format(pos="c"))[0].get('src')
data_dict['no_snapshot'] = quad_data.xpath(self.QUADRANT.format(pos="d"))[0].text
except IndexError:
return {}
try:
state = quad_data.xpath(self.QUADRANT.format(pos="b"))[0].get('style')
try:
state = state.split('"')[1]
except IndexError:
state = state.split("'")[1]
state = os.path.split(state)[1]
state = os.path.splitext(state)[0]
except IndexError:
state = ''
data_dict['state'] = state
try:
policy = quad_data.xpath(self.QUADRANT.format(pos="g"))[0].get('src')
except:
policy = None
data_dict['policy'] = policy
return data_dict
[docs]def InstanceEntity(): # noqa
""" Temporary wrapper for Instance Entity during transition to JS based Entity
"""
return VersionPick({
Version.lowest(): NonJSInstanceEntity,
'5.9': JSInstanceEntity,
})
[docs]class SelectTable(Table):
"""Wigdet for non-editable table. used for selecting value"""
[docs] def fill(self, values):
"""Clicks on item - fill by selecting required value"""
value = values.get('name', '<None>')
changed = False
if value != self.currently_selected:
changed = True
self.row(name=value).click()
return changed
@property
def currently_selected(self):
"""Return Name of the selected row"""
selected = self.browser.elements(
".//tr[@class='selected']/td[1]",
parent=self)
result = map(self.browser.text, selected)
if len(result) == 0:
self.logger.info('Nothing is currently selected')
return None
else:
return result[0]
[docs] def read(self):
return self.currently_selected
[docs] def read_content(self):
"""This is a default Table.read() method for those who will need table content"""
return super(SelectTable, self).read()
[docs]class VMEntities(BaseEntitiesView):
"""
Entities view for vms/instances collection destinations
"""
@property
def entity_class(self):
return InstanceEntity().pick(self.browser.product_version)
paginator = PaginationPane()
adv_search_clear = Text('//div[@id="main-content"]//h1//span[@id="clear_search"]/a')
[docs]class HostAllVMsView(BaseLoggedInPage):
"""
This view is used in test_host_relationships
"""
title = Text(".//div[@id='main-content']//h1")
@property
def is_displayed(self):
if self.browser.product_version < "5.9":
title = "{} (All VMs)".format(self.context["object"].name)
else:
title = "{} (All Direct VMs)".format(self.context["object"].name)
return (
self.navigation.currently_selected == ["Compute", "Infrastructure", "Hosts"] and
self.title.text == title
)
[docs]class ProviderAllVMsView(BaseLoggedInPage):
"""
This view is used in test_provider_relationships
"""
title = Text(".//div[@id='main-content']//h1")
@property
def is_displayed(self):
msg = "{} (All Direct VMs)".format(self.context["object"].name)
return (
self.navigation.currently_selected == ["Compute", "Infrastructure", "Providers"] and
self.title.text == msg
)
[docs]class VMDetailsEntities(View):
"""
Details entities view for vms/instances details destinations
VM's have 3-4 more tables, should inherit and add them there.
"""
title = Text('//div[@id="main-content"]//h1//span[@id="explorer_title_text"]')
properties = SummaryTable(title='Properties')
lifecycle = SummaryTable(title='Lifecycle')
relationships = SummaryTable(title='Relationships')
vmsafe = SummaryTable(title='VMsafe')
attributes = SummaryTable(title='Custom Attributes') # Only displayed when assigned
compliance = SummaryTable(title='Compliance')
power_management = SummaryTable(title='Power Management')
security = SummaryTable(title='Security')
configuration = SummaryTable(title='Configuration')
diagnostics = SummaryTable(title='Diagnostics')
smart_management = SummaryTable(title='Smart Management')
datastore_allocation_summary = SummaryTable(title='Datastore Allocation Summary')
[docs]class VMPropertyDetailView(View):
title = Text('//div[@id="main-content"]//h1//span[@id="explorer_title_text"]')
table = Table('//div[@id="gtl_div"]//table')
paginator = PaginationPane()
[docs]class ProvisionView(BaseLoggedInPage):
"""
The provisioning view, with nested ProvisioningForm as `form` attribute.
Handles template selection before Provisioning form with `before_fill` method
"""
title = Text('#explorer_title_text')
breadcrumb = BreadCrumb()
@View.nested
class form(BasicProvisionFormView): # noqa
"""First page of provision form is image selection
Second page of form is tabbed with nested views
"""
image_table = Table('//div[@id="pre_prov_div"]//table')
continue_button = Button('Continue') # Continue button on 1st page, image selection
submit_button = Button('Submit') # Submit for 2nd page, tabular form
cancel_button = Button('Cancel')
def before_fill(self, values):
# Provision from image is a two part form,
# this completes the image selection before the tabular parent form is filled
template_name = values.get('template_name',
self.parent_view.context['object'].template_name)
provider_name = self.parent_view.context['object'].provider.name
try:
row = self.image_table.row(name=template_name,
provider=provider_name)
except IndexError:
raise TemplateNotFound('Cannot find template "{}" for provider "{}"'
.format(template_name, provider_name))
row.click()
self.continue_button.click()
# TODO timing, wait_displayed is timing out and can't get it to stop in is_displayed
sleep(3)
self.flush_widget_cache()
@property
def is_displayed(self):
return False
[docs]class CloneVmView(BaseLoggedInPage):
title = Text('#explorer_title_text')
@View.nested
class form(BasicProvisionFormView): # noqa
submit_button = Button('Submit')
cancel_button = Button('Cancel')
@property
def is_displayed(self):
return False
[docs]class MigrateVmView(BaseLoggedInPage):
title = Text('#explorer_title_text')
@View.nested
class form(BasicProvisionFormView): # noqa
submit = Button('Submit')
cancel = Button('Cancel')
@property
def is_displayed(self):
# Nothing is shown
return False
[docs]class RetirementView(BaseLoggedInPage):
"""
Set Retirement date view for vms/instances
The title actually as Instance|VM.VM_TYPE string in it, otherwise the same
"""
title = Text('#explorer_title_text')
@View.nested
class form(View): # noqa
retirement_date = Calendar(name='retirementDate')
remove_date = Image(locator='.//div[@id="retirement_date_div"]//a/img[@alt="Set to blank"]')
retirement_warning = BootstrapSelect(id='retirementWarning')
entities = View.nested(BaseNonInteractiveEntitiesView)
save = Button('Save')
cancel = Button('Cancel')
@property
def is_displayed(self):
# TODO match quadicon and title
return False
[docs]class RetirementViewWithOffset(RetirementView):
"""The form portion, with 59z+ offset mode selection"""
@View.nested
class form(View): # noqa
retirement_mode = BootstrapSelect(id='formMode')
retirement_date = ConditionalSwitchableView(reference='retirement_mode')
@retirement_date.register('Specific Date and Time', default=True)
class RetirementDateSelectionView(View):
datetime_select = TextInput(id='retirement_date_datepicker')
@retirement_date.register('Time Delay from Now')
class RetirementOffsetSelectionView(View):
# TODO unique widget for these touchspin elements, with singular fill method
# will allow for consistent fill of view.form
months = TextInput(name='months')
weeks = TextInput(name='weeks')
days = TextInput(name='days')
hours = TextInput(name='hours')
retirement_offset_datetime = Text(
locator='.//div[@id="retirement_date_result_div"]/input[@id="retirement_date"]')
retirement_warning = BootstrapSelect(id='retirementWarning')
entities = View.nested(BaseNonInteractiveEntitiesView)
save = Button('Save')
cancel = Button('Cancel')
[docs]class EditView(BaseLoggedInPage):
"""
Edit vms/instance page
The title actually as Instance|VM.VM_TYPE string in it, otherwise the same
"""
title = Text('#explorer_title_text')
@View.nested
class form(View): # noqa
"""The form portion of the view"""
custom_identifier = TextInput(id='custom_1')
description = TextInput(id='description')
parent_vm = BootstrapSelect(id='chosen_parent')
# MultiBoxSelect element only has table ID in CFME 5.8+
# https://bugzilla.redhat.com/show_bug.cgi?id=1463265
child_vms = MultiBoxSelect(id='child-vm-select')
save_button = Button('Save')
reset_button = Button('Reset')
cancel_button = Button('Cancel')
@property
def is_displayed(self):
# Only name is displayed
return False
[docs]class SetOwnershipView(BaseLoggedInPage):
"""
Set vms/instance ownership page
The title actually as Instance|VM.VM_TYPE string in it, otherwise the same
"""
@View.nested
class form(View): # noqa
user_name = BootstrapSelect('user_name')
group_name = BootstrapSelect('group_name')
entities = View.nested(BaseNonInteractiveEntitiesView)
save_button = Button('Save')
reset_button = Button('Reset')
cancel_button = Button('Cancel')
@property
def is_displayed(self):
# TODO match quadicon using entities, no provider match through icon asset yet
return False
[docs]class ManagementEngineView(BaseLoggedInPage):
"""
Edit management engine relationship page
The title actually as Instance|VM.VM_TYPE string in it, otherwise the same
"""
@View.nested
class form(View): # noqa
server = BootstrapSelect('server_id')
save_button = Button('Save')
reset_button = Button('Reset')
cancel_button = Button('Cancel')
@property
def is_displayed(self):
# Only the name is displayed
return False
[docs]class PolicySimulationView(BaseLoggedInPage):
"""
Policy Simulation page for vms/instances
"""
@View.nested
class form(View): # noqa
policy = BootstrapSelect('policy_id')
# TODO policies table, ability to remove
entities = View.nested(BaseNonInteractiveEntitiesView)
cancel_button = Button('Cancel')
@property
def is_displayed(self):
# TODO match quadicon
return False
[docs]class RightSizeView(BaseLoggedInPage):
"""
Right Size recommendations page for vms/instances
"""
# TODO new table widget for right-size tables
# They're H3 headers with the table as following-sibling
@property
def is_displayed(self):
# Only name is displayed
return False
[docs]class DriftHistory(BaseLoggedInPage):
title = Text('#explorer_title_text')
breadcrumb = BreadCrumb(locator='.//ol[@class="breadcrumb"]')
history_table = Table(locator='.//div[@id="main_div"]/table')
analyze_button = Button(title="Select up to 10 timestamps for Drift Analysis")
@property
def is_displayed(self):
return (
"Drift History" in self.title.text and
self.history_table.is_displayed
)
[docs]class DriftAnalysis(BaseLoggedInPage):
title = Text('#explorer_title_text')
apply_button = Button("Apply")
drift_sections = CheckableBootstrapTreeview(tree_id="all_sectionsbox")
drift_analysis = DriftComparison(locator=".//div[@id='compare-grid']")
@View.nested
class toolbar(View): # noqa
all_attributes = Button(title="All attributes")
different_values_attributes = Button(title="Attributes with different values")
same_values_attributes = Button(title="Attributes with same values")
details_mode = Button(title="Details Mode")
exists_mode = Button(title="Exists Mode")
@property
def is_displayed(self):
return (
self.title.text == 'Drift for VM or Template "{}"'.format(self.context["object"].name)
)