# -*- 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, TableRow, Text, TextInput, ParametrizedView, Image, ConditionalSwitchableView)
from widgetastic_patternfly import (
BreadCrumb, Dropdown, BootstrapSelect, Tab, Input, CheckableBootstrapTreeview)
from cfme.base.login import BaseLoggedInPage
from cfme.exceptions import TemplateNotFound
from cfme.utils.log import logger
from widgetastic_manageiq import (
Calendar, Checkbox, Button, ItemsToolBarViewSelector, Table, MultiBoxSelect, RadioGroup,
VersionPick, Version, BaseEntitiesView, NonJSBaseEntity, BaseListEntity, BaseQuadIconEntity,
BaseTileIconEntity, JSBaseEntity, BaseNonInteractiveEntitiesView, PaginationPane,
DriftComparison, ParametrizedSummaryTable
)
[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]
state = state.split("-")[1]
except IndexError:
state = ''
data_dict['state'] = state
try:
policy = quad_data.xpath(self.QUADRANT.format(pos="g"))[0].get('src')
except Exception:
policy = None
data_dict['policy'] = policy
else:
data_dict['os'] = data_dict['quad']['topLeft']['tooltip']
data_dict['vendor'] = data_dict['quad']['bottomLeft']['tooltip']
data_dict['no_snapshots'] = data_dict['total_snapshots']
data_dict['state'] = data_dict['quad']['topRight']['tooltip']
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 SelectableTableRow(TableRow):
@property
def selected(self):
return 'selected' in self.browser.classes(self)
[docs]class SelectTable(Table):
"""Wigdet for non-editable table. used for selecting value"""
Row = SelectableTableRow
[docs] def fill(self, values):
"""Clicks on item - fill by selecting required values"""
if self.row(**values).selected:
return False
else:
self.row(**values).click()
return True
@property
def currently_selected(self):
"""Return values of the selected row"""
for row in self.rows:
if row.selected:
return row.read()
else:
self.logger.info('Nothing is currently selected')
return None
[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"]')
summary = ParametrizedView.nested(ParametrizedSummaryTable)
[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()
image_table = Table('//div[@id="pre_prov_div"]//table')
@View.nested
class form(BasicProvisionFormView): # noqa
"""First page of provision form is image selection
Second page of form is tabbed with nested views
"""
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')
provider_name = values.get('provider_name')
if template_name is None or provider_name is None:
logger.error('template_name "{}", or provider_name "{}" not passed to '
'provisioning form', template_name, provider_name)
# try to find the template anyway, even if values weren't passed
try:
row = self.parent.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):
return False
[docs]class PublishVmView(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 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)
)