Merge "Add Grafana CPU metrics test"

This commit is contained in:
Jenkins 2016-09-20 14:50:49 +00:00 committed by Gerrit Code Review
commit e5bdc8d9e6
7 changed files with 584 additions and 0 deletions

View File

View File

@ -0,0 +1,189 @@
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import contextlib
import selenium.common.exceptions as Exceptions
from selenium import webdriver
from selenium.webdriver.remote import webelement
import selenium.webdriver.support.ui as Support
from selenium.webdriver.support import expected_conditions
from six.moves.urllib import parse
class ImproperlyConfigured(Exception):
"""Raises on some errors in pages classes configuration"""
pass
class BaseWebObject(object):
def __init__(self, driver, timeout=5):
self.driver = driver
self.timeout = timeout
def _turn_off_implicit_wait(self):
self.driver.implicitly_wait(0)
def _turn_on_implicit_wait(self):
self.driver.implicitly_wait(self.timeout)
@contextlib.contextmanager
def waits_disabled(self):
try:
self._turn_off_implicit_wait()
yield
finally:
self._turn_on_implicit_wait()
def _is_element_present(self, *locator):
with self.waits_disabled():
try:
self._get_element(*locator)
return True
except Exceptions.NoSuchElementException:
return False
def _is_element_visible(self, *locator):
try:
return self._get_element(*locator).is_displayed()
except (Exceptions.NoSuchElementException,
Exceptions.ElementNotVisibleException):
return False
def _is_element_displayed(self, element):
if element is None:
return False
try:
if isinstance(element, webelement.WebElement):
return element.is_displayed()
else:
return element.src_elem.is_displayed()
except (Exceptions.ElementNotVisibleException,
Exceptions.StaleElementReferenceException):
return False
def _is_text_visible(self, element, text, strict=True):
if not hasattr(element, 'text'):
return False
if strict:
return element.text == text
else:
return text in element.text
def _get_element(self, *locator):
return self.driver.find_element(*locator)
def _get_elements(self, *locator):
return self.driver.find_elements(*locator)
def _fill_field_element(self, data, field_element):
field_element.clear()
field_element.send_keys(data)
return field_element
def _select_dropdown(self, value, element):
select = Support.Select(element)
select.select_by_visible_text(value)
def _select_dropdown_by_value(self, value, element):
select = Support.Select(element)
select.select_by_value(value)
def _get_dropdown_options(self, element):
select = Support.Select(element)
return select.options
def get_action(self):
return webdriver.ActionChains(self.driver)
class PageObject(BaseWebObject):
"""Base class for page objects."""
URL = None
def __init__(self, driver):
"""Constructor."""
super(PageObject, self).__init__(driver)
self._page_title = None
@property
def page_title(self):
return self.driver.title
def open(self):
if self.URL is None:
raise ImproperlyConfigured('`open` method requires {!r} has '
'not None URL class variable')
url = parse.urljoin(self.get_current_page_url(), self.URL)
self.driver.get(url)
return self
@contextlib.contextmanager
def wait_for_page_load(self, timeout=10):
old_page = self.driver.find_element_by_tag_name('html')
yield
Support.WebDriverWait(
self.driver,
timeout).until(expected_conditions.staleness_of(old_page))
def is_the_current_page(self, do_assert=False):
found_expected_title = self.page_title.startswith(self._page_title)
if do_assert:
err_msg = ("Expected to find %s in page title, instead found: %s" %
(self._page_title, self.page_title))
assert found_expected_title, err_msg
return found_expected_title
def get_current_page_url(self):
return self.driver.current_url
def close_window(self):
return self.driver.close()
def is_nth_window_opened(self, n):
return len(self.driver.window_handles) == n
def switch_window(self, name=None, index=None):
"""Switches focus between the webdriver windows.
Args:
- name: The name of the window to switch to.
- index: The index of the window handle to switch to.
If the method is called without arguments it switches to the
last window in the driver window_handles list.
In case only one window exists nothing effectively happens.
Usage:
page.switch_window(name='_new')
page.switch_window(index=2)
page.switch_window()
"""
if name is not None and index is not None:
raise ValueError("switch_window receives the window's name or "
"the window's index, not both.")
if name is not None:
self.driver.switch_to.window(name)
elif index is not None:
self.driver.switch_to.window(self.driver.window_handles[index])
else:
self.driver.switch_to.window(self.driver.window_handles[-1])
def go_to_previous_page(self):
self.driver.back()
def go_to_next_page(self):
self.driver.forward()
def refresh_page(self):
self.driver.refresh()

View File

@ -0,0 +1,205 @@
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from selenium.webdriver.common import by
from fuel_ccp_tests.helpers.ui import base_pages
class LoginPage(base_pages.PageObject):
_login_username_field_locator = (by.By.NAME, 'username')
_login_password_field_locator = (by.By.NAME, 'password')
_login_submit_button_locator = (by.By.CLASS_NAME, "btn")
URL = '/login'
def __init__(self, driver):
super(LoginPage, self).__init__(driver)
self._page_title = "Grafana"
@property
def username_field(self):
return self._get_element(*self._login_username_field_locator)
@property
def password_field(self):
return self._get_element(*self._login_password_field_locator)
@property
def login_button(self):
return self._get_element(*self._login_submit_button_locator)
def login(self, username, password):
self.open()
self._fill_field_element(username, self.username_field)
self._fill_field_element(password, self.password_field)
with self.wait_for_page_load():
self.login_button.click()
page = MainPage(self.driver)
page.is_the_current_page(do_assert=True)
return page
class DashboardPage(base_pages.PageObject):
_submenu_controls_locator = (by.By.CLASS_NAME, "submenu-controls")
_submenu_item_xpath_tpl = (
'.//*[contains(@class, "submenu-item")]'
'[.//*[text()="{}"]]'
'//*[contains(@class, "variable-link-wrapper")]')
_hostname_selector = (by.By.XPATH,
_submenu_item_xpath_tpl.format('Hostname:'))
_disk_selector = (by.By.XPATH, _submenu_item_xpath_tpl.format('Disks:'))
_interface_selector = (by.By.XPATH,
_submenu_item_xpath_tpl.format('Interface:'))
_filesystem_selector = (by.By.XPATH,
_submenu_item_xpath_tpl.format('Filesystem:'))
_variable_option_selector = (by.By.CLASS_NAME, 'variable-option')
_panel_selector = (by.By.TAG_NAME, 'grafana-panel')
_panel_title_text_selector = (by.By.CLASS_NAME, 'panel-title-text')
_tooltip_selector = (by.By.ID, 'tooltip')
_tooltip_series_name_selector = (by.By.CLASS_NAME,
'graph-tooltip-series-name')
_tooltip_series_value_selector = (by.By.CLASS_NAME, 'graph-tooltip-value')
def __init__(self, driver, dashboard_name):
super(DashboardPage, self).__init__(driver)
self._page_title = "Grafana - {}".format(dashboard_name)
def is_dashboards_page(self):
return (self.is_the_current_page() and
self._is_element_visible(*self._submenu_controls_locator))
def get_back_to_home(self):
self.go_to_previous_page()
page = MainPage(self.driver)
page.is_the_current_page(do_assert=True)
return page
@property
def submenu(self):
return self._get_element(*self._submenu_controls_locator)
def _get_submenu_list(self, selector):
submenu_item_list = self.submenu.find_element(*selector)
submenu_item_list.click()
return submenu_item_list.find_elements(*self._variable_option_selector)
def _get_submenu_items_names(self, selector):
return [x.text for x in self._get_submenu_list(selector)]
def _choose_submenu_item_value(self, selector, value):
list_items = self._get_submenu_list(selector)
mapping = {x.text.lower(): x for x in list_items}
mapping[value].click()
def get_hostnames_list(self):
return self._get_submenu_items_names(self._hostname_selector)
def choose_hostname(self, value):
return self._choose_submenu_item_value(self._hostname_selector, value)
def get_disks_list(self):
return self._get_submenu_items_names(self._disk_selector)
def choose_disk(self, value):
return self._choose_submenu_item_value(self._disk_selector, value)
def get_interfaces_list(self):
return self._get_submenu_items_names(self._interface_selector)
def choose_interface(self, value):
return self._choose_submenu_item_value(self._interface_selector, value)
def get_filesystems_list(self):
return self._get_submenu_items_names(self._filesystem_selector)
def choose_filesystem(self, value):
return self._choose_submenu_item_value(self._filesystem_selector,
value)
def _get_panels_mapping(self):
panels = self._get_elements(*self._panel_selector)
return {x.find_element(*self._panel_title_text_selector).text: x
for x in panels}
def get_cpu_panel(self):
return self._get_panels_mapping()['CPU']
def get_panel_tooltip(self, panel):
size = panel.size
action = self.get_action()
action.move_to_element_with_offset(panel, size['width'] / 2,
size['height'] / 2)
action.perform()
return self._get_element(*self._tooltip_selector)
def get_tooltop_values(self, tooltip):
result = {}
series_names = tooltip.find_elements(
*self._tooltip_series_name_selector)
series_values = tooltip.find_elements(
*self._tooltip_series_value_selector)
for series_name, series_value in zip(series_names, series_values):
result[series_name.text.strip(':')] = series_value.text
return result
class MainPage(base_pages.PageObject):
_dropdown_menu_locator = (by.By.LINK_TEXT, 'Home')
_dashboards_list_locator = (by.By.CLASS_NAME, 'search-results-container')
_dashboard_locator = (by.By.CLASS_NAME, 'search-result-link')
def __init__(self, driver):
super(MainPage, self).__init__(driver)
self._page_title = "Grafana - Home"
def is_main_page(self):
return (self.is_the_current_page() and
self._is_element_visible(*self._dropdown_menu_locator))
@property
def dropdown_menu(self):
return self._get_element(*self._dropdown_menu_locator)
@property
def dashboards_list(self):
self.open_dropdown_menu()
return self._get_element(*self._dashboards_list_locator)
@property
def dashboards(self):
return self.dashboards_list.find_elements(*self._dashboard_locator)
def is_dropdown_menu_opened(self):
return self._is_element_present(*self._dashboards_list_locator)
def open_dropdown_menu(self):
if not self.is_dropdown_menu_opened():
self.dropdown_menu.click()
def open_dashboard(self, dashboard_name):
dashboards_mapping = {dashboard.text.lower(): dashboard
for dashboard in self.dashboards}
dashboards_mapping[dashboard_name.lower()].click()
dashboard_page = DashboardPage(self.driver, dashboard_name)
dashboard_page.is_dashboards_page()
return dashboard_page

View File

@ -0,0 +1,35 @@
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
# Use a virtual display server for running the tests headless or not
headless_mode = os.environ.get('SELENIUM_HEADLESS', False)
# The browser session will be started with given proxy,
# can be useful if you try to start UI tests on developer machine,
# but environment is on remote server
proxy_address = os.environ.get('DRIVER_PROXY', None)
# Maximize the current window that webdriver is using or not
maximize_window = os.environ.get('SELENIUM_MAXIMIZE', True)
# Sets a sticky timeout to implicitly wait for an element to be found,
# or a command to complete.
implicit_wait = os.environ.get('IMPLICIT_WAIT', 5)
# Set the amount of time to wait for a page load to complete
# before throwing an error.
page_timeout = os.environ.get('PAGE_TIMEOUT', 15)

View File

@ -0,0 +1,79 @@
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import contextlib
import socket
from selenium import webdriver
from selenium.webdriver.common import by
from selenium.webdriver.common import proxy
import xvfbwrapper
from fuel_ccp_tests.helpers.ui import ui_settings
@contextlib.contextmanager
def ui_driver(url):
vdisplay = None
# Start a virtual display server for running the tests headless.
if ui_settings.headless_mode:
vdisplay = xvfbwrapper.Xvfb(width=1920, height=1080)
args = []
# workaround for memory leak in Xvfb taken from:
# http://blog.jeffterrace.com/2012/07/xvfb-memory-leak-workaround.html
args.append("-noreset")
# disables X access control
args.append("-ac")
if hasattr(vdisplay, 'extra_xvfb_args'):
# xvfbwrapper 0.2.8 or newer
vdisplay.extra_xvfb_args.extend(args)
else:
vdisplay.xvfb_cmd.extend(args)
vdisplay.start()
driver = get_driver(url)
try:
yield driver
finally:
driver.quit()
if vdisplay is not None:
vdisplay.stop()
def get_driver(url, anchor='/html', by_selector_type=by.By.XPATH):
proxy_address = ui_settings.proxy_address
# Increase the default Python socket timeout from nothing
# to something that will cope with slow webdriver startup times.
# This *just* affects the communication between this test process
# and the webdriver.
socket.setdefaulttimeout(60)
# Start the Selenium webdriver and setup configuration.
proxy_ex = None
if proxy_address is not None:
proxy_ex = proxy.Proxy(
{
'proxyType': proxy.ProxyType.MANUAL,
'socksProxy': proxy_address,
}
)
driver = webdriver.Firefox(proxy=proxy_ex)
if ui_settings.maximize_window:
driver.maximize_window()
driver.implicitly_wait(ui_settings.implicit_wait)
driver.set_page_load_timeout(ui_settings.page_timeout)
driver.get(url)
driver.find_element(by_selector_type, anchor)
return driver

View File

@ -9,3 +9,5 @@ docker-compose==1.7.1
urllib3
psycopg2
python-k8sclient==0.3.0
selenium
xvfbwrapper

View File

@ -0,0 +1,74 @@
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import re
import pytest
from fuel_ccp_tests.helpers import ext
from fuel_ccp_tests.helpers.ui import ui_tester
from fuel_ccp_tests.helpers.ui import grafana_pages
@pytest.yield_fixture
def driver(request):
url = os.environ.get('GRAFANA_URL')
if url is None:
underlay = request.getfixturevalue('underlay')
nodes = underlay.node_names()
ip = underlay.host_by_node_name(nodes[0])
k8s_actions = request.getfixturevalue('k8s_actions')
k8sclient = k8s_actions.api
service = k8sclient.services.get('grafana', namespace='ccp')
port = service.spec.ports[0].node_port
url = "http://{}:{}/".format(ip, port)
with ui_tester.ui_driver(url) as driver:
yield driver
@pytest.fixture
def system_dashboard(request, driver):
"""Login and return grafana system dashboard page"""
login_page = grafana_pages.LoginPage(driver).open()
main_page = login_page.login(username='admin', password='admin')
return main_page.open_dashboard('system')
@pytest.mark.revert_snapshot(ext.SNAPSHOT.ccp_deployed)
class TestGrafana(object):
def test_cpu_metrics(self, system_dashboard):
"""Check CPU metrics on Grafana system dashboard
Scenario:
* Login to Grafana
* Go to system dashboard page
* Select 1'st hostname
* Move mouse to CPU graph
* Check that "user", "system", "idle" values are present on tooltip
* Repeat last 3 steps for each hostname
"""
for host in system_dashboard.get_hostnames_list():
system_dashboard.choose_hostname(host)
cpu_panel = system_dashboard.get_cpu_panel()
tooltip = system_dashboard.get_panel_tooltip(cpu_panel)
tooltip_values = system_dashboard.get_tooltop_values(tooltip)
for key in ("user", "system", "idle"):
err_msg = ("Grafana CPU panel tooltip "
"doesn't contains {} value").format(key)
assert key in tooltip_values, err_msg
err_msg = ("Grafana CPU panel tooltip value for {} "
"is 0% or has wrong format").format(key)
assert re.search(r'[1-9][0-9]*?%',
tooltip_values[key]) is not None, err_msg