.
**/
require_once dirname(__FILE__) . '/../../include/CWebTest.php';
define('CURRENT_YEAR', date("Y"));
/**
* @onBefore prepareHostDashboardsData
*
* @backup hosts
*/
class testDashboardsHostDashboardPage extends CWebTest {
const HOST_NAME = 'Host for Host Dashboards';
const TEMPLATE_NAME = 'Template for '.self::HOST_NAME;
const COUNT_MANY = 20;
public function prepareHostDashboardsData() {
$data = [
'host_name' => self::HOST_NAME,
'dashboards' => [
[
'name' => 'Dashboard 1',
'pages' => [
[
'name' => 'Page 1',
'widgets' => [
[
'type' => 'svggraph',
'name' => 'Graph widget',
'width' => 6,
'height' => 4,
'fields' => [
[
'type' => ZBX_WIDGET_FIELD_TYPE_INT32,
'name' => '*',
'value' => 0
]
]
]
]
],
[
'name' => 'Page 2'
]
]
]
]
];
$this->createHostWithDashboards($data);
// Create a Host with many Dashboards and another Host with many Pages.
$dashboard_array = [];
$page_array = [];
for ($i = 1; $i <= self::COUNT_MANY; $i++) {
$dashboard_array[] = ['name' => 'Dashboard '.$i];
$page_array[] = ['name' => 'Page '.$i];
}
$data_dashboards = [
'host_name' => 'Many Dashboards',
'dashboards' => $dashboard_array
];
$this->createHostWithDashboards($data_dashboards);
$data_pages = [
'host_name' => 'Many Pages',
'dashboards' => [
[
'name' => 'Dashboard 1',
'pages' => $page_array
]
]
];
$this->createHostWithDashboards($data_pages);
}
/**
* Check layout.
*/
public function testDashboardsHostDashboardPage_Layout() {
$this->openDashboardsForHost(self::HOST_NAME);
$this->page->assertTitle('Dashboards');
$this->page->assertHeader('Host dashboards');
$breadcrumbs = $this->query('class:breadcrumbs')->one();
$this->assertEquals('zabbix.php?action=host.view', $breadcrumbs->query('link:All hosts')->one()->getAttribute('href'));
$this->assertEquals(['All hosts', self::HOST_NAME], $breadcrumbs->query('tag:li')->all()->asText());
$host_dashboard_navigation = $this->query('class:host-dashboard-navigation')->one();
$prev_button = $host_dashboard_navigation->query('xpath:.//button[@title="Previous dashboard"]')->one();
$this->assertTrue($prev_button->isDisplayed());
$this->assertFalse($prev_button->isEnabled());
$dashboard_tab = $host_dashboard_navigation->query('xpath:.//span[text()="Dashboard 1"]')->one();
$this->assertEquals('Dashboard 1', $dashboard_tab->getAttribute('title'));
// Assert the listed dashboard dropdown.
$list_button = $host_dashboard_navigation->query('xpath:.//button[@title="Dashboard list"]')->one();
$this->assertTrue($list_button->isClickable());
$list_button->click();
$popup_menu = $list_button->asPopupButton()->getMenu();
$this->assertEquals(['Dashboard 1'], $popup_menu->getItems()->asText());
$popup_menu->close();
$next_button = $host_dashboard_navigation->query('xpath:.//button[@title="Next dashboard"]')->one();
$this->assertTrue($next_button->isDisplayed());
$this->assertFalse($next_button->isEnabled());
// Check page tabs.
$dashboard_navigation = $this->query('class:dashboard-navigation')->one();
$this->assertEquals(['Page 1', 'Page 2'], $dashboard_navigation->query('xpath:.//li[@class="sortable-item"]')->all()->asText());
// Check Slideshow button.
foreach (['Stop', 'Start'] as $status) {
$this->assertTrue($dashboard_navigation->query('xpath:.//button/span[text()="'.$status.' slideshow"]')->one()->isDisplayed());
$dashboard_navigation->query('xpath:.//button['.CXPathHelper::fromClass('btn-dashboard-toggle-slideshow').']')->one()->click();
}
}
/**
* Open and close the Kiosk mode.
*/
public function testDashboardsHostDashboardPage_CheckKioskMode() {
$this->openDashboardsForHost(self::HOST_NAME);
// Test Kiosk mode.
$this->query('xpath://button[@title="Kiosk mode"]')->one()->click();
$this->page->waitUntilReady();
// Check that Header and Filter disappeared.
$this->query('xpath://h1[@id="page-title-general"]')->waitUntilNotVisible();
$this->assertFalse(CFilterElement::find()->one()->isVisible());
$this->assertFalse($this->query('class:host-dashboard-navigation')->exists());
$this->assertTrue(CDashboardElement::find()->one()->getWidgets()->first()->isVisible());
// Check that Breadcrumbs are still visible.
$breadcrumbs = $this->query('xpath://ul[@class="breadcrumbs"]//span')->all();
$this->assertEquals(['All hosts', self::HOST_NAME], $breadcrumbs->asText());
foreach ($breadcrumbs as $breadcrumb) {
$this->assertTrue($breadcrumb->isVisible());
}
// Check Dashboard page controls.
foreach (['Previous page', 'Stop slideshow', 'Next page'] as $button) {
$this->assertTrue($this->query('xpath://button[@title="'.$button.'"]')->exists());
}
// Check Slideshow button.
$dashboard_controls = $this->query('class:dashboard-kioskmode-controls')->one();
foreach (['Stop', 'Start'] as $status) {
$this->assertTrue($dashboard_controls->query('xpath:.//button[@title="'.$status.' slideshow"]')->one()->isDisplayed());
$dashboard_controls->query('xpath:.//button['.
CXPathHelper::fromClass('btn-dashboard-kioskmode-toggle-slideshow').']')->one()->click();
}
$this->query('xpath://button[@title="Normal view"]')->waitUntilPresent()->one()->hoverMouse()->click();
$this->page->waitUntilReady();
// Check that Header and Filter are visible again.
$this->query('xpath://h1[@id="page-title-general"]')->waitUntilVisible();
foreach (['xpath://div['.CXPathHelper::fromClass('filter-space').']', 'class:host-dashboard-navigation', 'class:dashboard'] as $selector) {
$this->assertTrue($this->query($selector)->exists());
}
}
public function getCheckFiltersData() {
return [
[
[
'fields' => ['id:from' => 'now-2h', 'id:to' => 'now-1h'],
'expected_tab' => 'now-2h – now-1h',
'zoom_buttons' => [
'js-btn-time-left' => true,
'btn-time-zoomout' => true,
'js-btn-time-right' => true
]
]
],
[
[
'fields' => ['id:from' => 'now-2y', 'id:to' => 'now-1y'],
'expected_tab' => 'now-2y – now-1y',
'zoom_buttons' => [
'js-btn-time-left' => true,
'btn-time-zoomout' => true,
'js-btn-time-right' => true
]
]
],
[
[
'link' => 'Last 30 days',
'expected_fields' => ['id:from' => 'now-30d', 'id:to' => 'now'],
'zoom_buttons' => [
'js-btn-time-left' => true,
'btn-time-zoomout' => true,
'js-btn-time-right' => false
]
]
],
[
[
'link' => 'Last 2 years',
'expected_fields' => ['id:from' => 'now-2y', 'id:to' => 'now'],
'zoom_buttons' => [
'js-btn-time-left' => true,
'btn-time-zoomout' => false,
'js-btn-time-right' => false
]
]
],
[
[
'fields' => ['id:from' => CURRENT_YEAR.'-01-01 00:00:00', 'id:to' => CURRENT_YEAR.'-01-01 01:00:00'],
'expected_tab' => CURRENT_YEAR.'-01-01 00:00:00 – '.CURRENT_YEAR.'-01-01 01:00:00',
'zoom_buttons' => [
'js-btn-time-left' => true,
'btn-time-zoomout' => true,
'js-btn-time-right' => true
]
]
],
[
[
'fields' => ['id:from' => '2023-01', 'id:to' => '2023-01'],
'expected_fields' => ['id:from' => '2023-01-01 00:00:00', 'id:to' => '2023-01-31 23:59:59'],
'expected_tab' => '2023-01-01 00:00:00 – 2023-01-31 23:59:59',
'zoom_buttons' => [
'js-btn-time-left' => true,
'btn-time-zoomout' => true,
'js-btn-time-right' => true
]
]
],
[
[
'fields' => ['id:from' => '$#^$@', 'id:to' => ' '],
'error' => [
'from' => 'Invalid date.',
'to' => 'Invalid date.'
]
]
],
[
[
'fields' => ['id:from' => 'now-3y', 'id:to' => 'now'],
'error' => [
'from' => 'Maximum time period to display is {days} days.'
],
'days_count' => true
]
]
];
}
/**
* Change values in the filter section and check the resulting changes.
*
* @dataProvider getCheckFiltersData
*/
public function testDashboardsHostDashboardPage_CheckFilters($data) {
$this->openDashboardsForHost(self::HOST_NAME);
$filter = CFilterElement::find()->one();
$form = $filter->asForm(['normalized' => true]);
// Set custom time filter.
if (CTestArrayHelper::get($data, 'fields')) {
$form->fill($data['fields']);
$form->query('id:apply')->one()->click();
}
else {
$form->query('link', $data['link'])->waitUntilClickable()->one()->click();
}
$this->page->waitUntilReady();
// Check error message if such is expected.
if (CTestArrayHelper::get($data, 'error')) {
foreach ($data['error'] as $field => $text) {
// Count of days mentioned in error depends ot presence of leap year february in selected period.
if (CTestArrayHelper::get($data, 'days_count')) {
$text = str_replace('{days}', CDateTimeHelper::countDays('now', 'P2Y'), $text);
}
$message = $this->query('xpath://ul[@data-error-for='.CXPathHelper::escapeQuotes($field).']//li')->one();
$this->assertEquals($text, $message->getText());
}
}
else {
// If error not expected.
// Check Zoom buttons.
foreach ($data['zoom_buttons'] as $button => $state) {
$this->assertTrue($this->query('xpath://button[contains(@class, '.CXPathHelper::escapeQuotes($button).
')]')->one()->isEnabled($state)
);
}
// Check field values.
$form->checkValue(CTestArrayHelper::get($data, 'expected_fields', CTestArrayHelper::get($data, 'fields')));
// Check tab title.
$this->assertEquals(
CTestArrayHelper::get($data, 'expected_tab', CTestArrayHelper::get($data, 'link')),
$filter->getSelectedTabName()
);
}
}
public function getCheckNavigationTabsData() {
return [
[
[
'host_name' => 'One Dashboard - one Page',
'dashboards' => [['name' => 'Dashboard 1']]
]
],
[
[
'host_name' => 'One Dashboard - three Pages',
'dashboards' => [
[
'name' => 'Dashboard 1',
'pages' => [['name' => 'Page 1'], ['name' => 'Page 2'], ['name' => 'Page 3']]
]
]
]
],
[
[
'host_name' => 'Three Dashboards - three Pages each',
'dashboards' => [
[
'name' => 'Dashboard 1',
'pages' => [['name' => 'Page 11'], ['name' => 'Page 12'], ['name' => 'Page 13']]
],
[
'name' => 'Dashboard 2',
'pages' => [['name' => 'Page 21'], ['name' => 'Page 22'], ['name' => 'Page 23']]
],
[
'name' => 'Dashboard 3',
'pages' => [['name' => 'Page 31'], ['name' => 'Page 32'], ['name' => 'Page 33']]
]
]
]
],
[
[
'host_name' => 'Unicode Dashboards',
'dashboards' => [
['name' => '🙂🙃'],
['name' => 'test тест 测试 テスト ทดสอบ'],
['name' => ''],
['name' => ' &'],
['name' => '☺♥²©™"\'']
]
]
],
[
[
'host_name' => 'Unicode Pages',
'dashboards' => [
[
'name' => 'Dashboard 1',
'pages' => [
['name' => '🙂🙃'],
['name' => 'test тест 测试 テスト ทดสอบ'],
['name' => ''],
['name' => ' &'],
['name' => '☺♥²©™"\'']
]
]
]
]
],
[
[
'host_name' => 'Long names',
'dashboards' => [
[
'name' => STRING_255,
'pages' => [['name' => STRING_255], ['name' => STRING_128]]
]
]
]
]
];
}
/**
* Check Dashboard and Page navigation using tabs.
*
* @dataProvider getCheckNavigationTabsData
*/
public function testDashboardsHostDashboardPage_CheckNavigationTabs($data) {
// Create the required entities in database.
$api_dashboards = $this->createHostWithDashboards($data);
$this->openDashboardsForHost($data['host_name']);
// Parent to all Dashboard navigation elements.
$navigation = $this->query('class:host-dashboard-navigation')->one();
// Assert buttons.
$prev_button = $navigation->query('xpath:.//button[@title="Previous dashboard"]')->one();
$this->assertFalse($prev_button->isEnabled());
$next_button = $navigation->query('xpath:.//button[@title="Next dashboard"]')->one();
$this->assertEquals(count($api_dashboards) > 1, $next_button->isEnabled());
// Assert dashboard Tabs and Pages.
foreach ($api_dashboards as $dashboard) {
$dashboard_tab = $navigation->query('xpath:.//span[text()='.CXPathHelper::escapeQuotes($dashboard['name']).']')->one();
$this->assertEquals($dashboard['name'], $dashboard_tab->getAttribute('title'));
// If not already on the correct Dashboard, then switch.
if ($dashboard['name'] !== $navigation->query('xpath:.//div[@class="selected-tab"]')->one()->getText()) {
$dashboard_tab->click();
$this->page->waitUntilReady();
}
/*
* Check Page switching.
* It is expected that in every page there will be a Widget named like so: 'Dashboard 1 - Page 2 widget'.
*/
if (count($dashboard['pages']) === 1) {
// Case when there is only one Page. The Page button is not even visible.
$this->checkDashboardOpen($dashboard);
}
else {
// When a Dashboard contains several Pages.
// Check that Slideshow button exists.
$this->assertTrue($this->query('class:btn-dashboard-toggle-slideshow')->exists());
// Parent to all Page tabs.
$page_tabs = $this->query('class:dashboard-navigation-tabs')->one();
foreach ($dashboard['pages'] as $page) {
$page_tab = $page_tabs->query('xpath:.//span[text()='.CXPathHelper::escapeQuotes($page['name']).']')->one();
$this->assertEquals($page['name'], $page_tab->getAttribute('title'));
// Only switch the Page if it is not the first one.
if ($page['name'] !== $page_tabs->query('xpath:.//div[@class="selected-tab"]')->one()->getText()) {
$page_tab->click();
$this->page->waitUntilReady();
}
// Assert that the Dashboard has opened.
$this->checkDashboardOpen($dashboard, $page);
}
}
}
// Assert the Dashboard dropdown.
$list_button = $navigation->query('xpath:.//button[@title="Dashboard list"]')->one();
$list_button->click();
$popup_menu = $list_button->asPopupButton()->getMenu();
$this->assertEquals(array_column($api_dashboards, 'name'), $popup_menu->getItems()->asText());
$popup_menu->close();
}
public function getCheckNavigationButtonsData() {
return [
[
[
'host_name' => 'Many Dashboards',
'previous_button_selector' => 'class:btn-host-dashboard-previous-dashboard',
'next_button_selector' => 'class:btn-host-dashboard-next-dashboard'
]
],
[
[
'host_name' => 'Many Pages',
'previous_button_selector' => 'class:btn-dashboard-previous-page',
'next_button_selector' => 'class:btn-dashboard-next-page'
]
]
];
}
/**
* Check Dashboard and Page navigation using the buttons.
*
* @dataProvider getCheckNavigationButtonsData
*/
public function testDashboardsHostDashboardPage_CheckNavigationButtons($data) {
$this->openDashboardsForHost($data['host_name']);
$previous = $this->query($data['previous_button_selector'])->one();
$next = $this->query($data['next_button_selector'])->one();
// If these are set then use them instead of the counter for determining the correct widget name.
$dashboard_count = ($data['host_name'] === 'Many Dashboards') ? null : 1;
$page_count = ($data['host_name'] === 'Many Pages') ? null : 1;
// Cycle tabs in forward direction (by using the > button).
for ($i = 1; $i <= self::COUNT_MANY; $i++) {
$this->checkDashboardOpen(
['name' => 'Dashboard '.($dashboard_count === null ? $i : $dashboard_count)],
['name' => 'Page '.($page_count === null ? $i : $page_count)]
);
//Assert if enabled/disabled correctly.
$this->assertEquals($i > 1, $previous->isEnabled());
$this->assertEquals($i < self::COUNT_MANY, $next->isEnabled());
// Only switch the Dashboard if it is not the last one.
if ($i !== self::COUNT_MANY) {
$next->click();
$this->page->waitUntilReady();
}
}
// Cycle tabs in backward direction (by using the < button).
for ($i = self::COUNT_MANY; $i >= 1; $i--) {
$this->checkDashboardOpen(
['name' => 'Dashboard '.($dashboard_count === null ? $i : $dashboard_count)],
['name' => 'Page '.($page_count === null ? $i : $page_count)]
);
//Assert if enabled/disabled correctly.
$this->assertEquals($i > 1, $previous->isEnabled());
$this->assertEquals($i < self::COUNT_MANY, $next->isEnabled());
// Only switch the Dashboard if it is not the first one.
if ($i !== 1) {
$previous->click();
$this->page->waitUntilReady();
}
}
}
/**
* Check Dashboard navigation using the dropdown.
*/
public function testDashboardsHostDashboardPage_CheckNavigationDropdown() {
// Create a Host with some Dashboards.
$data = [
'host_name' => 'Dashboards for dropdown',
'dashboards' => [
['name' => 'Dashboard 1'],
['name' => '🙂🙃'],
['name' => ''],
['name' => 'test тест 测试 テスト ทดสอบ'],
['name' => ' &☺♥²©™"\'']
]
];
$api_dashboards = $this->createHostWithDashboards($data);
// Open the newly created Host dashboard.
$this->openDashboardsForHost('Dashboards for dropdown');
// Click each Dashboard in the menu and assert that it opened.
foreach ($api_dashboards as $dashboard) {
$navigation = $this->query('class:host-dashboard-navigation')->one();
// Only switch if not already on the correct Dashboard.
if ($dashboard['name'] !== $navigation->query('xpath:.//div[@class="selected-tab"]')->one()->getText()) {
$list_button = $this->query('xpath:.//button[@title="Dashboard list"]')->one();
$list_button->click();
$list_button->asPopupButton()->getMenu()->select($dashboard['name']);
}
$this->checkDashboardOpen($dashboard);
}
}
/**
* Opens the 'Host dashboards' page for a specific host.
*
* @param $host_name name of the Host to open Dashboards for
*/
protected function openDashboardsForHost($host_name) {
// Instead of searching the Host in the UI it is faster to just get the ID from the database.
$id = CDBHelper::getValue('SELECT hostid FROM hosts WHERE host='.zbx_dbstr($host_name));
$this->page->login()->open('zabbix.php?action=host.dashboard.view&hostid='.$id)->waitUntilReady();
}
/**
* Creates a Template with required Dashboards using API and assigns it to a new Host.
*
* @param $data data from data provider
*
* @returns array dashboard data, that was actually sent to the API (with the defaults set)
*/
protected function createHostWithDashboards($data) {
$response = CDataHelper::createTemplates([
[
'host' => 'Template for '.$data['host_name'],
'groups' => [
['groupid' => '1'] // template group 'Templates'
]
]
]);
$template_id = $response['templateids']['Template for '.$data['host_name']];
CDataHelper::createHosts([
[
'host' => $data['host_name'],
'groups' => [
['groupid' => '6'] // host group 'Virtual machines'
],
'templates' => [
'templateid' => $template_id
]
]
]);
// Add all resulting dashboard data and then return.
$api_dashboards = [];
foreach ($data['dashboards'] as $dashboard) {
// Set Template ID.
$dashboard['templateid'] = $template_id;
// Add the default Dashboard Page if none set.
if (!array_key_exists('pages', $dashboard)) {
$dashboard['pages'] = [
[
'name' => 'Page 1',
'widgets' => [
[
'type' => 'clock',
'name' => $this->widgetName($dashboard['name'], 'Page 1'),
'width' => 6,
'height' => 4
]
]
]
];
}
// Add default widgets if missing, the name is important.
foreach ($dashboard['pages'] as $i => $page) {
if (!array_key_exists('widgets', $dashboard['pages'][$i])) {
$dashboard['pages'][$i]['widgets'] = [
[
'type' => 'clock',
'name' => $this->widgetName($dashboard['name'], $page['name']),
'width' => 6,
'height' => 4
]
];
}
}
// Create the Dashboard with API.
CDataHelper::call('templatedashboard.create', [
$dashboard
]);
$api_dashboards[] = $dashboard;
}
// The dashboard tabs are sorted alphabetically.
CTestArrayHelper::usort($api_dashboards, ['name']);
return $api_dashboards;
}
/**
* Create a widget name from the dashboard and page name.
* The name is used for making sure the correct Dashboard is displayed.
*
* @param $dashboard_name name of the dashboard this widget is on
* @param $page_name name of the page this widget is on
*
* @return string calculated widget name
*/
protected function widgetName($dashboard_name, $page_name) {
// Widget name max length 255.
return substr($dashboard_name.' - '.$page_name.' widget', 0, 255);
}
/**
* Check that the correct Dashboard and Pages is displayed.
* This is done by testing for the unique Widget name.
*
* @param $dashboard Dashboard data array
* @param $page Page data array
*/
protected function checkDashboardOpen ($dashboard, $page = null) {
if ($page === null) {
$page = $dashboard['pages'][0];
}
// Check that the correct Dashboard tab is selected.
$navigation = $this->query('class:host-dashboard-navigation')->one();
$this->assertEquals($dashboard['name'], $navigation->query('xpath:.//div[@class="selected-tab"]')->one()->getText());
// Check that the correct Page tab is selected.
if (count(CTestArrayHelper::get($dashboard, 'pages', [])) > 1) {
$page_tabs = $this->query('class:dashboard-navigation-tabs')->one();
$this->assertEquals($page['name'], $page_tabs->query('xpath:.//div[@class="selected-tab"]')->one()->getText());
}
// Assert correct Dashboard is displayed by asserting the Widget name.
CDashboardElement::find()->one()->getWidget($this->widgetName($dashboard['name'], $page['name']));
}
}