You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
zabbix/ui/tests/selenium/testPageAdministrationGener...

1407 lines
45 KiB

1 year ago
<?php
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
require_once dirname(__FILE__).'/../include/CWebTest.php';
require_once dirname(__FILE__).'/traits/TableTrait.php';
require_once dirname(__FILE__).'/behaviors/CMessageBehavior.php';
/**
* @backup module, widget
*/
class testPageAdministrationGeneralModules extends CWebTest {
use TableTrait;
/**
* Attach MessageBehavior to the test.
*
* @return array
*/
public function getBehaviors() {
return [
'class' => CMessageBehavior::class
];
}
private static $dashboardid;
private static $template_dashboardid;
private static $hostid;
const TEMPLATEID = 50000;
const ITEMID = 400410;
const INACCESSIBLE_TEXT = 'No permissions to referred object or it does not exist!';
const INACCESSIBLE_XPATH = 'xpath:.//div[contains(@class, "dashboard-widget-inaccessible")]';
const HOSTNAME = 'Host for widget module test';
private static $widget_descriptions = [
'Action log' => 'Displays records about executed action operations (notifications, remote commands).',
'Clock' => 'Displays local, server, or specified host time.',
'Data overview' => 'Displays the latest item data and current status of each item for selected hosts.',
'Discovery status' => 'Displays the status summary of the active network discovery rules.',
'Favorite graphs' => 'Displays shortcuts to the most needed graphs (marked as favorite).',
'Favorite maps' => 'Displays shortcuts to the most needed network maps (marked as favorite).',
'Gauge' => 'Displays the value of a single item as gauge.',
'Geomap' => 'Displays hosts as markers on a geographical map.',
'Graph' => 'Displays data of up to 50 items as line, points, staircase, or bar charts.',
'Graph (classic)' => 'Displays a single custom graph or a simple graph.',
'Graph prototype' => 'Displays a grid of graphs created by low-level discovery from either a graph prototype or '.
'an item prototype.',
'Host availability' => 'Displays the host count by status (available/unavailable/unknown).',
'Item value' => 'Displays the value of a single item prominently.',
'Map' => 'Displays either a single configured network map or one of the configured network maps in the map '.
'navigation tree.',
'Map navigation tree' => 'Allows to build a hierarchy of existing maps and display problem statistics for each '.
'included map and map group.',
'Plain text' => 'Displays the latest data for the selected items in plain text.',
'Problem hosts' => 'Displays the problem count by host group and the highest problem severity within a group.',
'Problems' => 'Displays currently open problems with quick access links to the problem details.',
'Problems by severity' => 'Displays the problem count by severity.',
'SLA report' => 'Displays SLA reports.',
'System information' => 'Displays the current status and system statistics of the Zabbix server and its '.
'associated components.',
'Top hosts' => 'Displays top N hosts that have the highest or the lowest item value (for example, CPU load) '.
'with an option to add progress-bar visualizations and customize report columns.',
'Top triggers' => 'Displays top N triggers that have the most problems within the period of evaluation,'.
' sorted by the number of problems.',
'Trigger overview' => 'Displays trigger states for selected hosts.',
'URL' => 'Displays the content retrieved from the specified URL.',
'Web monitoring' => 'Displays the status summary of the active web monitoring scenarios.'
];
/**
* Creates dashboards with widgets and defines the corresponding dashboard IDs.
*/
public static function prepareDashboardData() {
$response = CDataHelper::call('dashboard.create', [
[
'name' => 'Dashboard for widget module testing',
'private' => 0,
'auto_start' => 0,
'pages' => [
[
'name' => 'Map page',
'widgets' => [
[
'type' => 'navtree',
// TODO: Uncomment the below line when ZBX-22245 will be resolved.
// 'name' => 'Awesome map tree',
'x' => 0,
'y' => 0,
'width' => 12,
'height' => 4,
'view_mode' => 0,
'fields' => [
[
'type' => 1,
'name' => 'reference',
'value' => 'GZCSV'
],
[
'type' => 1,
'name' => 'navtree.name.1',
'value' => 'Awesome map'
],
[
'type' => 8,
'name' => 'navtree.sysmapid.1',
'value' => 1
]
]
],
[
'type' => 'map',
'x' => 12,
'y' => 0,
'width' => 12,
'height' => 4,
'view_mode' => 0,
'fields' => [
[
'type' => 0,
'name' => 'source_type',
'value' => 2
],
[
'type' => 1,
'name' => 'filter_widget_reference',
'value' => 'GZCSV'
]
]
],
[
'type' => 'favgraphs',
'view_mode' => 0,
'x' => 6,
'y' => 4,
'width' => 6,
'height' => 4
]
]
],
[
'name' => 'Alarm clock page',
'widgets' => [
[
'type' => 'clock345',
'view_mode' => 0,
'x' => 0,
'y' => 0,
'width' => 6,
'height' => 4
],
[
'type' => 'favgraphs',
'view_mode' => 0,
'x' => 6,
'y' => 0,
'width' => 6,
'height' => 4
]
]
],
[
'name' => 'System info page',
'widgets' => [
[
'type' => 'favgraphs',
'view_mode' => 0,
'x' => 0,
'y' => 0,
'width' => 6,
'height' => 4
],
[
'type' => 'systeminfo',
'view_mode' => 0,
'x' => 6,
'y' => 0,
'width' => 6,
'height' => 4
]
]
],
[
'name' => 'Empty widget page',
'widgets' => [
[
'type' => 'favgraphs',
'view_mode' => 0,
'x' => 0,
'y' => 0,
'width' => 6,
'height' => 4
],
[
'type' => 'emptyWidget',
'view_mode' => 0,
'x' => 6,
'y' => 0,
'width' => 6,
'height' => 4
]
]
]
]
]
]);
self::$dashboardid = $response['dashboardids'][0];
$template_responce = CDataHelper::call('templatedashboard.create', [
[
'templateid' => self::TEMPLATEID,
'name' => 'Templated dashboard for module widgets',
'auto_start' => 0,
'pages' => [
[
'name' => 'Default clock page',
'widgets' => [
[
'type' => 'clock',
// TODO: Uncomment the below line when ZBX-22245 will be resolved.
// 'name' => 'Default clock',
'width' => 6,
'height' => 4
],
[
'type' => 'item',
'x' => 6,
'y' => 0,
'width' => 6,
'height' => 4,
'fields' => [
[
'type' => 0,
'name' => 'itemid',
'value' => self::ITEMID
]
]
]
]
],
[
'name' => 'Alarm clock page',
'widgets' => [
[
'type' => 'clock',
'name' => 'Clock widget',
'width' => 6,
'height' => 4
],
[
'type' => 'clock345',
'view_mode' => 0,
'x' => 6,
'y' => 0,
'width' => 6,
'height' => 4,
'fields' => [
[
'type' => 0,
'name' => 'time_type',
'value' => 1
],
[
'type' => 1,
'name' => 'tzone_timezone',
'value' => 'local'
]
]
]
]
]
]
]
]);
self::$template_dashboardid = $template_responce['dashboardids'][0];
$host_responce = CDataHelper::createHosts([
[
'host' => self::HOSTNAME,
'interfaces' => [
'type' => INTERFACE_TYPE_AGENT,
'main' => 1,
'useip' => 1,
'ip' => '127.0.0.1',
'dns' => '',
'port' => '10050'
],
'groups' => [
'groupid' => 7
],
'status' => HOST_STATUS_MONITORED,
'templates' => [
'templateid' => self::TEMPLATEID
]
]
]);
self::$hostid = $host_responce['hostids'][self::HOSTNAME];
}
public function testPageAdministrationGeneralModules_Layout() {
$modules = [
[
'Name' => '1st Module name',
'Version' => '1',
'Author' => '1st Module author',
'Description' => '1st Module description',
'Status' => 'Disabled'
],
[
'Name' => '2nd Module name !@#$%^&*()_+',
'Version' => 'two !@#$%^&*()_+',
'Author' => '2nd Module author !@#$%^&*()_+',
'Description' => 'Module description !@#$%^&*()_+',
'Status' => 'Disabled'
],
[
'Name' => '4th Module',
'Version' => '',
'Author' => '',
'Description' => '',
'Status' => 'Disabled'
],
[
'Name' => '5th Module',
'Version' => '',
'Author' => '',
'Description' => 'Adding top-level and sub-level menu',
'Status' => 'Disabled'
],
[
'Name' => 'Clock2',
'Version' => '1.1',
'Author' => 'Zabbix QA department',
'Description' => '',
'Status' => 'Disabled'
],
[
'Name' => 'Empty widget',
'Version' => '1.0',
'Author' => 'Some Zabbix employee',
'Description' => '',
'Status' => 'Disabled'
],
[
'Name' => 'шестой модуль',
'Version' => 'бета 2',
'Author' => 'Работник Заббикса',
'Description' => 'Удалить "Reports" из меню верхнего уровня, а так же удалить "Maps" из секции "Monitoring".',
'Status' => 'Disabled'
]
];
// Create an array with widget modules that should be present by default.
$widget_modules = [];
$i = 0;
foreach (self::$widget_descriptions as $name => $description) {
$widget_modules[$i]['Name'] = $name;
$widget_modules[$i]['Version'] = '1.0';
$widget_modules[$i]['Author'] = 'Zabbix';
$widget_modules[$i]['Description'] = $description;
$widget_modules[$i]['Status'] = 'Enabled';
$i++;
}
// Open modules page and check header.
$this->page->login()->open('zabbix.php?action=module.list');
$this->assertEquals('Modules', $this->query('tag:h1')->one()->getText());
// Check status of buttons on the modules page.
foreach (['Scan directory' => true, 'Enable' => false, 'Disable' => false] as $button => $enabled) {
$this->assertTrue($this->query('button', $button)->one()->isEnabled($enabled));
}
$table = $this->query('class:list-table')->asTable()->one();
// Check that only widget modules are present until the 'Scan directory' button is pressed.
$this->assertTableData($widget_modules);
$count = $table->getRows()->count();
$this->assertTableStats($count);
$this->assertEquals('0 selected', $this->query('id:selected_count')->one()->getText());
// Check modules table headers.
$headers = $table->getHeadersText();
// Remove empty element from headers array.
array_shift($headers);
$this->assertSame(['Name', 'Version', 'Author', 'Description', 'Status'], $headers);
// Load modules.
$this->loadModules();
$all_modules = array_merge($widget_modules, $modules);
$total_count = count($all_modules);
// Sort column contents ascending.
usort($all_modules, function($a, $b) {
return strcmp($a['Name'], $b['Name']);
});
// Check parameters of modules in the modules table.
$this->assertTableData($all_modules);
$count = CDBHelper::getCount('SELECT moduleid FROM module');
$this->assertEquals('Displaying '.$total_count.' of '.$total_count.' found', $this->query('class:table-stats')
->one()->getText()
);
// Load modules again and check that no new modules were added.
$this->loadModules(false);
$this->assertEquals('Displaying '.$count.' of '.$count.' found', $this->query('class:table-stats')->one()->getText());
}
public function getModuleDetails() {
return [
// Module 1.
[
[
'Name' => '1st Module name',
'Version' => '1',
'Author' => '1st Module author',
'Description' => '1st Module description',
'Directory' => 'modules/module_number_1',
'Namespace' => 'Modules\Example_A',
'URL' => '1st module URL',
'Enabled' => false
]
],
// Module 2.
[
[
'Name' => '2nd Module name !@#$%^&*()_+',
'Version' => 'two !@#$%^&*()_+',
'Author' => '2nd Module author !@#$%^&*()_+',
'Description' => 'Module description !@#$%^&*()_+',
'Directory' => 'modules/module_number_2',
'Namespace' => 'Modules\Example_B',
'URL' => '!@#$%^&*()_+',
'Enabled' => false
]
],
// Module 4.
[
[
'Name' => '4th Module',
'Version' => '',
'Author' => '-',
'Description' => '-',
'Directory' => 'modules/module_number_4',
'Namespace' => 'Modules\Example_A',
'URL' => '-',
'Enabled' => false
]
],
// Module 5.
[
[
'Name' => '5th Module',
'Version' => '',
'Author' => '-',
'Description' => 'Adding top-level and sub-level menu',
'Directory' => 'modules/module_number_5',
'Namespace' => 'Modules\Example_E',
'URL' => '-',
'Enabled' => false
]
],
// Clock2.
[
[
'Name' => 'Clock2',
'Version' => '1.1',
'Author' => 'Zabbix QA department',
'Description' => '-',
'Directory' => 'modules/clock32',
'Namespace' => 'Modules\Clock2',
'URL' => '-',
'Enabled' => false
]
],
// Empty widget.
[
[
'Name' => 'Empty widget',
'Version' => '1.0',
'Author' => 'Some Zabbix employee',
'Description' => '-',
'Directory' => 'modules/emptyWidget',
'Namespace' => 'Modules\emptyWidget',
'URL' => '-',
'Enabled' => false
]
],
// Module 6.
[
[
'Name' => 'шестой модуль',
'Version' => 'бета 2',
'Author' => 'Работник Заббикса',
'Description' => 'Удалить "Reports" из меню верхнего уровня, а так же удалить "Maps" из секции "Monitoring".',
'Directory' => 'modules/module_number_6',
'Namespace' => 'Modules\Example_F',
'URL' => '-',
'Enabled' => false
]
]
];
}
/**
* @dataProvider getModuleDetails
* @depends testPageAdministrationGeneralModules_Layout
*/
public function testPageAdministrationGeneralModules_Details($data) {
// Open corresponding module from the modules table.
$this->page->login()->open('zabbix.php?action=module.list');
$this->query('link', $data['Name'])->waitUntilVisible()->one()->click();
$dialog = COverlayDialogElement::find()->one()->waitUntilReady();
$form = $dialog->asForm();
// Check value af every field in Module details form.
foreach ($data as $key => $value) {
$this->assertEquals($value, $form->getFieldContainer($key)->getText());
}
$dialog->close();
}
public function getModuleData() {
return [
// Enable only 1st module - '1st Module' entry added under Monitoring.
[
[
[
'module_name' => '1st Module name',
'menu_entries' => [
[
'name' => '1st Module',
'action' => 'first.module',
'message' => 'If You see this message - 1st module is working'
]
]
]
]
],
// Enable only 2nd Module - '2nd Module' entry added under Monitoring.
[
[
[
'module_name' => '2nd Module name !@#$%^&*()_+',
'menu_entries' => [
[
'name' => '2nd Module',
'action' => 'second.module',
'message' => '2nd module is also working'
]
]
]
]
],
// Enable both 1st and 2nd module - '1st Module' and '2nd Module' entries added under Monitoring.
[
[
[
'module_name' => '1st Module name',
'menu_entries' => [
[
'name' => '1st Module',
'action' => 'first.module',
'message' => 'If You see this message - 1st module is working'
]
]
],
[
'module_name' => '2nd Module name !@#$%^&*()_+',
'menu_entries' => [
[
'name' => '2nd Module',
'action' => 'second.module',
'message' => '2nd module is also working'
]
]
]
]
],
// Attempting to enable two modules that use identical namespace.
[
[
[
'module_name' => '1st Module name',
'menu_entries' => [
[
'name' => '1st Module',
'action' => 'first.module',
'message' => 'If You see this message - 1st module is working'
]
]
],
[
'expected' => TEST_BAD,
'module_name' =>'4th Module',
'menu_entries' => [
[
'name' => '4th Module',
'action' => 'forth.module'
]
],
'error_details' => 'Identical namespace (Modules\Example_A) is used by modules located at '.
'modules/module_number_1, modules/module_number_4.'
]
]
],
// Enable 5th Module - Module 5 menu top level menu is added with 3 entries.
[
[
[
'module_name' => '5th Module',
'top_menu_entry' => 'Module 5 menu',
'menu_entries' => [
[
'name' => 'Your profile',
'action' => 'userprofile.edit',
'message' => 'User profile: Zabbix Administrator',
'check_disabled' => false
],
[
'name' => 'пятый модуль',
'action' => 'fifth.module',
'message' => 'Если ты это читаешь то 5ый модуль работает'
],
[
'name' => 'Module list',
'action' => 'module.list',
'message' => 'Modules',
'check_disabled' => false
]
]
]
]
],
// Enable шестой модуль - Top level menu Reports and menu entry Maps are removed.
[
[
[
'module_name' => 'шестой модуль',
'remove' => true,
'top_menu_entry' => 'Reports',
'menu_entry' => 'Maps'
]
]
]
];
}
/**
* @backupOnce module
* @dataProvider getModuleData
* @depends testPageAdministrationGeneralModules_Layout
*/
public function testPageAdministrationGeneralModules_EnableDisable($data) {
$this->page->login()->open('zabbix.php?action=module.list');
foreach (['list', 'form'] as $view) {
// This block is separate because one of the cases requires one module to be enabled before the other to succeed.
foreach ($data as $module) {
// Enable module and check the success or error message.
$this->enableModule($module, $view);
}
// In case if module should be enabled, check that changes took place and then disable each enabled module.
foreach ($data as $module) {
if (CTestArrayHelper::get($module, 'expected', TEST_GOOD) === TEST_GOOD) {
$this->assertModuleEnabled($module);
$this->disableModule($module, $view);
$this->assertModuleDisabled($module);
}
}
}
}
public function getFilterData() {
return [
// Exact name match.
[
[
'filter' => [
'Name' => '1st Module name'
],
'expected' => [
'1st Module name'
]
]
],
// Partial name match for all 3 modules.
[
[
'filter' => [
'Name' => 'Module'
],
'expected' => [
'1st Module name',
'2nd Module name !@#$%^&*()_+',
'4th Module',
'5th Module'
]
]
],
// Partial name match with space in between.
[
[
'filter' => [
'Name' => 'le n'
],
'expected' => [
'1st Module name',
'2nd Module name !@#$%^&*()_+'
]
]
],
// Filter by various characters in name.
[
[
'filter' => [
'Name' => '!@#$%^&*()_+'
],
'expected' => [
'2nd Module name !@#$%^&*()_+'
]
]
],
// Exact name match with leading and trailing spaces.
[
[
'filter' => [
'Name' => ' 4th Module '
],
'expected' => [
'4th Module'
]
]
],
// Retrieve only Enabled modules.
[
[
'filter' => [
'Status' => 'Enabled'
],
'expected' => array_merge(['2nd Module name !@#$%^&*()_+'], array_keys(self::$widget_descriptions))
]
],
// Retrieve only Disabled modules.
[
[
'filter' => [
'Status' => 'Disabled'
],
'expected' => [
'1st Module name',
'4th Module',
'5th Module',
'Clock2',
'Empty widget',
'шестой модуль'
]
]
],
// Retrieve only Disabled modules that have 'name' string in their name.
[
[
'filter' => [
'Name' => 'name',
'Status' => 'Disabled'
],
'expected' => [
'1st Module name'
]
]
]
];
}
/**
* @dataProvider getFilterData
* @depends testPageAdministrationGeneralModules_Layout
*/
public function testPageAdministrationGeneralModules_Filter($data) {
$this->page->login()->open('zabbix.php?action=module.list');
// Before checking the filter one of the modules needs to be enabled.
$table = $this->query('class:list-table')->asTable()->one();
$row = $table->findRow('Name', '2nd Module name !@#$%^&*()_+');
if ($row->getColumn('Status')->getText() !== 'Enabled') {
$row->query('link:Disabled')->one()->click();
}
// Apply and submit the filter from data provider.
$form = $this->query('name:zbx_filter')->asForm()->one();
$form->fill($data['filter']);
$form->submit();
$this->page->waitUntilReady();
// Check (using module name) that only the expected filters are returned in the list.
$this->assertTableDataColumn(CTestArrayHelper::get($data, 'expected'));
// Reset the filter and check that all loaded modules are displayed.
$this->query('button:Reset')->one()->click();
$count = CDBHelper::getCount('SELECT moduleid FROM module');
$this->assertEquals('Displaying '.$count.' of '.$count.' found', $this->query('class:table-stats')->one()->getText());
}
/**
* @depends testPageAdministrationGeneralModules_Layout
*/
public function testPageAdministrationGeneralModules_SimpleUpdate() {
$sql = 'SELECT * FROM module ORDER BY moduleid';
$initial_hash = CDBHelper::getHash($sql);
// Open one of the modules and update it without making any changes.
$this->page->login()->open('zabbix.php?action=module.list');
$this->query('link:1st Module name')->waitUntilVisible()->one()->click();
$this->page->waitUntilReady();
$this->query('button:Update')->one()->click();
$this->assertMessage(TEST_GOOD, 'Module updated');
// Check that Module has been updated and that there are no changes took place.
$this->assertEquals($initial_hash, CDBHelper::getHash($sql));
}
/**
* @depends testPageAdministrationGeneralModules_Layout
*/
public function testPageAdministrationGeneralModules_Cancel() {
$sql = 'SELECT * FROM module ORDER BY moduleid';
$initial_hash = CDBHelper::getHash($sql);
// Open the module update of which is going to be cancelled.
$this->page->login()->open('zabbix.php?action=module.list');
$this->query('link:1st Module name')->waitUntilVisible()->one()->click();
$this->page->waitUntilReady();
// Edit module status and Cancel the update.
$this->query('id:status')->asCheckbox()->one()->check();
$this->query('button:Cancel')->one()->click();
$this->page->waitUntilReady();
// Check that Module has been updated and that there are no changes took place.
$this->assertEquals($initial_hash, CDBHelper::getHash($sql));
}
public function getWidgetModuleData() {
return [
// Custom widget with JS, css and pre-defined widget type name
[
[
'module_name' => 'Clock2',
'widget_name' => 'Local',
'widget_type' => 'ALARM CLOCK',
'page' => 'Alarm clock page',
'refresh_rate' => '1 minute'
]
],
// Existing default widget.
[
[
'module_name' => 'System information',
'widget_name' => 'System information',
'page' => 'System info page'
]
],
// Existing default widget on which another widget is dependent.
[
[
'module_name' => 'Map navigation tree',
// TODO: Uncomment the below line and delete the line after it when ZBX-22245 will be resolved.
// 'widget_name' => 'Awesome map tree',
'widget_name' => 'Map navigation tree',
'dependent_widget' => 'Map',
'page' => 'Map page'
]
],
// Custom widget with minimal contents.
[
[
'module_name' => 'Empty widget',
'widget_name' => 'Empty widget',
'page' => 'Empty widget page',
'refresh_rate' => '2 minutes'
]
],
// Existing default widget on template dashboard.
[
[
'module_name' => 'Clock',
// TODO: Uncomment the below line and delete the line after it when ZBX-22245 will be resolved.
// 'widget_name' => 'Default clock',
'widget_name' => 'Local',
'template' => true,
'page' => 'Default clock page'
]
],
// Custom widget with minimal contents.
[
[
'module_name' => 'Clock2',
'widget_name' => 'Server',
'widget_type' => 'ALARM CLOCK',
'template' => true,
'not_available' => 'Empty widget',
'page' => 'Alarm clock page'
]
]
];
}
/**
* @onBeforeOnce prepareDashboardData
*
* @depends testPageAdministrationGeneralModules_Layout
*
* @dataProvider getWidgetModuleData
*/
public function testPageAdministrationGeneralModules_ChangeWidgetModuleStatus($module) {
$this->page->login()->open('zabbix.php?action=module.list');
// Determine the original status of the modules to be checked. Scenarios with mixed statuses are not considered.
$initial_status = $this->query('class:list-table')->asTable()->one()->findRow('Name', $module['module_name'])
->getColumn('Status')->getText();
if ($initial_status === 'Disabled') {
$this->enableModule($module, 'list');
$this->checkWidgetModuleStatus($module);
$this->disableModule($module, 'list');
$this->checkWidgetModuleStatus($module, 'disabled');
}
else {
$this->disableModule($module, 'list');
$this->checkWidgetModuleStatus($module, 'disabled');
$this->enableModule($module, 'list');
$this->checkWidgetModuleStatus($module);
}
}
public function getWidgetDimensions() {
return [
// Widget with pre-defined dimensions.
[
[
'module_name' => 'Clock2',
'widget_name' => 'Local',
'widget_type' => 'ALARM CLOCK',
'enable' => true,
'page' => 'Map page',
'dimensions' => ['width: 33.3333%', 'height: 280px']
]
],
// Widget with pre-defined dimensions on template.
[
[
'module_name' => 'Clock2',
'widget_name' => 'Local',
'widget_type' => 'ALARM CLOCK',
'page' => 'Alarm clock page',
'template' => true,
'dimensions' => ['width: 33.3333%', 'height: 280px']
]
],
// Widget with default dimensions.
[
[
'module_name' => 'Empty widget',
'widget_name' => 'Empty widget',
'widget_type' => 'Empty widget',
'enable' => true,
'page' => 'Map page',
'dimensions' => ['width: 50%', 'height: 350px']
]
]
];
}
/**
*
* @depends testPageAdministrationGeneralModules_ChangeWidgetModuleStatus
*
* @dataProvider getWidgetDimensions
*/
public function testPageAdministrationGeneralModules_CheckWidgetDimensions($data) {
$this->page->login();
if (array_key_exists('enable', $data)) {
$this->page->open('zabbix.php?action=module.list');
$this->enableModule($data, 'list');
}
$this->checkWidgetDimensions($data);
// Cancel editing dashboard not to interfere with following cases from data provider.
$this->query('link:Cancel')->one()->click();
}
/**
* Add a widget of a specific type to dashboard or template dashboard and check its default dimensions.
*
* @param array $data data provider.
*/
private function checkWidgetDimensions($data) {
// Open required dashboard page in edit mode.
$url = (array_key_exists('template', $data))
? 'zabbix.php?action=template.dashboard.edit&dashboardid='.self::$template_dashboardid
: 'zabbix.php?action=dashboard.view&dashboardid='.self::$dashboardid;
$this->page->open($url)->waitUntilReady();
$dashboard = CDashboardElement::find()->one()->waitUntilVisible();
$dashboard->selectPage($data['page']);
if (!array_key_exists('template', $data)) {
$dashboard->edit();
}
// Add widget from the data provider.
$widget_form = $dashboard->addWidget()->asForm();
$widget_form->fill(['Type' => CFormElement::RELOADABLE_FILL($data['widget_type'])]);
$widget_form->submit();
// Get widget dimensions from the style attribute of the widget grid element and compare with expected values.
$grid_selector = 'xpath:.//div[contains(@class, "dashboard-grid-widget-head")]/../..';
$widget_dimensions = $dashboard->getWidget($data['widget_name'])->query($grid_selector)->one()->getAttribute('style');
$dimension_array = array_map('trim', explode(';', $widget_dimensions));
foreach ($data['dimensions'] as $dimension) {
$this->assertContains($dimension, $dimension_array);
}
}
/**
* @depends testPageAdministrationGeneralModules_ChangeWidgetModuleStatus
*/
public function testPageAdministrationGeneralModules_DisableAllModules() {
$this->page->login()->open('zabbix.php?action=module.list')->waitUntilReady();
// Disable all modules.
$this->query('id:all_modules')->waitUntilPresent()->asCheckbox()->one()->set(true);
$this->query('button:Disable')->waitUntilCLickable()->one()->click();
$this->page->acceptAlert();
// Wait for the Success message to confirm that modules were disabled before heading to the dashboard.
$this->assertMessage(TEST_GOOD, 'Modules disabled');
// Open dashboard and check that all widgets are inaccessible.
$this->page->open('zabbix.php?action=dashboard.view&dashboardid='.self::$dashboardid)->waitUntilReady();
$this->checkAllWidgetsDisabledOnPage();
// Open template dashboard and check that all widgets are inaccessible.
$this->page->open('zabbix.php?action=template.dashboard.edit&dashboardid='.self::$template_dashboardid)->waitUntilReady();
$this->checkAllWidgetsDisabledOnPage();
// Open template dashboard on host and check that all widgets are inaccessible.
$this->page->open('zabbix.php?action=host.dashboard.view&hostid='.self::$hostid.'&dashboardid='.self::$template_dashboardid)
->waitUntilReady();
$this->checkAllWidgetsDisabledOnPage();
}
/**
* Check that all widgets that are displayed on opened dashboard page are inaccessible widgets.
*/
private function checkAllWidgetsDisabledOnPage() {
$dashboard = CDashboardElement::find()->one()->waitUntilPresent();
$total_count = $dashboard->getWidgets()->count();
$inaccessible_count = $dashboard->query(self::INACCESSIBLE_XPATH)->waitUntilVisible()->all()->count();
$this->assertEquals($total_count, $inaccessible_count);
}
/**
* Check widgets of the enabled/disabled modules are displayed in dashboards, host dashboard and template dashboard views.
*
* @param array $module module related information from data provider.
* @param string $status status of widget module before execution of this function.
*/
private function checkWidgetModuleStatus($module, $status = 'enabled') {
// Open dashboard or host dashboard and check widget display in this view.
$url = array_key_exists('template', $module)
? 'zabbix.php?action=host.dashboard.view&hostid='.self::$hostid.'&dashboardid='.self::$template_dashboardid
: 'zabbix.php?action=dashboard.view&dashboardid='.self::$dashboardid;
$this->page->open($url)->waitUntilReady();
$dashboard = CDashboardElement::find()->one()->waitUntilVisible();
$this->checkWidgetStatusOnDashboard($dashboard, $module, $status);
// Open Kiosk mode and check widget display again.
$this->checkWidgetStatusOnDashboard($dashboard, $module, $status, 'kiosk');
$this->query('xpath://button[@title="Normal view"]')->one()->click();
$this->page->waitUntilReady();
// Open dashboard in edit mode or open dashboard on template and check widget display again.
if (array_key_exists('template', $module)) {
$this->page->open('zabbix.php?action=template.dashboard.edit&dashboardid='.self::$template_dashboardid)
->waitUntilReady();
}
else {
$dashboard->edit();
}
$this->checkWidgetStatusOnDashboard($dashboard, $module, $status, 'edit');
// Check that widget is present among widget types dropdown.
$widget_dialog = $dashboard->addWidget();
$widget_type = (array_key_exists('widget_type', $module) ? $module['widget_type'] : $module['module_name']);
$options = $widget_dialog->asForm()->getField('Type')->asDropdown()->getOptions()->asText();
// Check that widget type is present in "Type" dropdown only if corresponding module is enabled.
$this->assertTrue(($status === 'enabled') ? in_array($widget_type, $options) : !in_array($widget_type, $options));
// Check that module that should be present only on regular dashboards is not present (key used only on template).
if (array_key_exists('not_available', $module)) {
$this->assertFalse(in_array($module['not_available'], $options));
}
// Go back to the list of modules after the check is complete.
$widget_dialog->close();
$this->page->open('zabbix.php?action=module.list');
}
/**
* Check enabled or disabled widget display and its parameters on a particular dashboard page.
* Requirements to the widget are dependent on corresponding module status and dashboard mode (view, kiosk, edit modes).
*
* @param CDashboardElement $dashboard dashboard that contains the corresponding module widget.
* @param array $module module related information from data provider.
* @param string $status status of widget module before execution of this function.
* @param string $mode mode of the dashboard.
*/
private function checkWidgetStatusOnDashboard($dashboard, $module, $status, $mode = null) {
$dashboard->selectPage($module['page']);
// Switch to kiosk mode if required.
if ($mode === 'kiosk') {
$this->query('xpath://button[@title="Kiosk mode"]')->one()->click();
$this->page->waitUntilReady();
}
if ($status === 'enabled') {
// Check that widget with required name is shown and that is doesn't have the inaccessilbe widget string in it.
$widget = $dashboard->getWidget($module['widget_name']);
$this->assertFalse($widget->query("xpath:.//div[text()=".CXPathHelper::escapeQuotes(self::INACCESSIBLE_TEXT).
"]")->one(false)->isValid()
);
// Check refresh interval if such specified in the data provider.
if (array_key_exists('refresh_rate', $module) && $mode !== 'edit') {
$this->assertEquals($module['refresh_rate'], $widget->getRefreshInterval());
CPopupMenuElement::find()->one()->close();
}
// Check that dependent widget is there and that it's content is not hidden.
if (array_key_exists('dependent_widget', $module)) {
$dependent_widget = $dashboard->getWidget($module['dependent_widget']);
$this->assertTrue($dependent_widget->isValid());
$this->assertNotEquals(self::INACCESSIBLE_TEXT, $dependent_widget->getContent()->getText());
}
}
else {
// Check that there is only 1 inaccessible widget present on the opened dashboard page.
$this->assertEquals(1, $dashboard->query(self::INACCESSIBLE_XPATH)->waitUntilVisible()->all()->count());
// Get the inaccessible widget and check its contents.
$inaccessible_widget = $dashboard->getWidget('Inaccessible widget');
$this->assertEquals(self::INACCESSIBLE_TEXT, $inaccessible_widget->getContent()->getText());
// Check that withget of the disabled module is not present on the dashboard.
$this->assertFalse($dashboard->getWidget($module['widget_name'], false)->isValid());
// Check that the dependent widget is still there, but its contents is not displayed.
if (array_key_exists('dependent_widget', $module)) {
$dependent_widget = $dashboard->getWidget($module['dependent_widget']);
$this->assertTrue($dependent_widget->isValid());
$this->assertEquals(self::INACCESSIBLE_TEXT, $dependent_widget->getContent()->getText());
}
/**
* Check that edit widget button on disabled module widget is hidden and that it doesn't exist
* if the dashboard is opened in Monitoring => Hosts view (where All hosts link is present) or in kiosk mode.
*/
$edit_button = $inaccessible_widget->query('xpath:.//button['.CXPathHelper::fromClass('js-widget-edit').']');
$this->assertFalse(($mode === 'kiosk' || $this->query('link:All hosts')->one(false)->isValid())
? $edit_button->one(false)->isValid()
: $edit_button->one()->isDisplayed()
);
// It should not be possible only to Delete the widget and only when the dashboard is in edit mode.
$button = $inaccessible_widget->query('xpath:.//button['.CXPathHelper::fromClass('js-widget-action').']')->one();
if ($mode === 'edit') {
$popup_menu = $button->waitUntilPresent()->asPopupButton()->getMenu();
$menu_items = $popup_menu->getItems();
$this->assertEquals(['Copy', 'Paste', 'Delete'], $menu_items->asText());
// Check that inaccessible widgets can only be deleted.
$this->assertEquals(['Delete'], array_values($menu_items->filter(CElementFilter::CLICKABLE)->asText()));
$popup_menu->close();
}
else {
$this->assertFalse($button->isVisible());
}
}
}
/**
* Function loads modules in frontend and checks the message depending on whether new modules were loaded.
*
* @param bool $first_load flag that determines whether modules are loaded for the first time.
*/
private function loadModules($first_load = true) {
// Load modules
$this->query('button:Scan directory')->waitUntilClickable()->one()->click();
$this->page->waitUntilReady();
// Check message after loading modules.
if ($first_load) {
// Each loaded module name is checked separately due to difference in their sorting on Jenkins and locally.
$this->assertMessage(TEST_GOOD, 'Modules updated', ['Modules added:', '1st Module name',
'2nd Module name !@#$%^&*()_+', '4th Module', '5th Module', 'Clock2', 'Empty widget', 'шестой модуль'
]);
}
else {
$this->assertMessage(TEST_GOOD, 'No new modules discovered');
}
}
/**
* Function checks if the corresponding menu entry exists, clicks on it and checks the URL and header of the page.
* If the module should remove a menu entry, the function makes sure that the corresponding menu entry doesn't exist.
*
* @param array $module module related information from data provider.
*/
private function assertModuleEnabled($module) {
$xpath = 'xpath://ul[@class="menu-main"]//a[text()="';
// If module removes a menu entry or top level menu entry, check that such entries are not present.
if (CTestArrayHelper::get($module, 'remove', false)) {
$this->assertEquals(0, $this->query($xpath.$module['menu_entry'].'"]')->count());
if (array_key_exists('top_menu_entry', $module)) {
$this->assertEquals(0, $this->query($xpath.$module['top_menu_entry'].'"]')->count());
}
return;
}
// If module adds single or multiple menu entries, open each corresponding view, check view header and URL.
$top_entry = CTestArrayHelper::get($module, 'top_menu_entry', 'Monitoring');
$this->query('link', $top_entry)->one()->waitUntilClickable()->click();
foreach ($module['menu_entries'] as $entry) {
sleep(1);
$this->query($xpath.$entry['name'].'"]')->one()->waitUntilClickable()->click();
$this->page->waitUntilReady();
$this->assertStringContainsString('zabbix.php?action='.$entry['action'], $this->page->getCurrentURL());
$this->assertEquals($entry['message'], $this->query('tag:h1')->waitUntilVisible()->one()->getText());
}
// Get back to modules list to enable or disable the next module.
$this->page->open('zabbix.php?action=module.list')->waitUntilReady();
}
/**
* Function checks if the corresponding menu entry is removed and url is not active after the module is disabled.
* If enabling the module removes a menu entry, the function checks that it is back after disabling the module.
*
* @param array $module module related information from data provider.
*/
private function assertModuleDisabled($module) {
$xpath = 'xpath://ul[@class="menu-main"]//li/a[text()="';
// If module removes a menu entry or top level menu entry, check that entries are back after disabling the module.
if (array_key_exists('remove', $module)) {
$this->assertEquals(1, $this->query($xpath.$module['menu_entry'].'"]')->count());
if (array_key_exists('top_menu_entry', $module)) {
$this->assertEquals(1, $this->query($xpath.$module['top_menu_entry'].'"]')->count());
}
return;
}
// If module adds single or multiple menu entries, check that entries don't exist after disabling the module.
foreach ($module['menu_entries'] as $entry) {
$check_entry = CTestArrayHelper::get($module, 'top_menu_entry', $entry['name']);
$this->assertEquals(0, $this->query($xpath.$check_entry.'"]')->count());
// In case if module many entry leads to an existing view, don't check that menu entry URL isn't available.
if (CTestArrayHelper::get($entry, 'check_disabled', true)) {
$this->page->open('zabbix.php?action='.$entry['action'])->waitUntilReady();
$message = CMessageElement::find()->one();
$this->assertStringContainsString('Page not found', $message->getText());
$this->page->open('zabbix.php?action=module.list');
}
}
}
/**
* Function enables module from the list in modules page or from module details form, depending on input parameters.
*
* @param array $data data array with module details
* @param string $view view from which the module should be enabled - module list or module details form.
*/
private function enableModule($module, $view) {
$expected = CTestArrayHelper::get($module, 'expected', TEST_GOOD);
// Change module status from Disabled to Enabled.
if ($view === 'form') {
$this->changeModuleStatusFromForm($module['module_name'], true, $expected);
}
else {
$this->changeModuleStatusFromPage($module['module_name'], 'Disabled');
}
// In case of negative test check error message and confirm that module wasn't applied.
if ($expected === TEST_BAD) {
$title = ($view === 'form') ? 'Cannot update module' : 'Cannot enable module';
$this->assertMessage($module['expected'], $title, $module['error_details']);
if ($view === 'form') {
COverlayDialogElement::find()->one()->close();
}
$this->assertModuleDisabled($module);
return;
}
// Check message and confirm that changes, made by the enabled module, took place.
$message = ($view === 'form') ? 'Module updated' : 'Module enabled';
$this->assertMessage($expected, $message);
CMessageElement::find()->one()->close();
}
/**
* Function disables module from the list in modules page or from module details form, depending on input parameters.
*
* @param array $module data array with module details
* @param string $view view from which the module should be enabled - module list or module details form.
*/
private function disableModule($module, $view) {
$expected = CTestArrayHelper::get($module, 'expected', TEST_GOOD);
// In case of negative test do nothing.
if ($expected === TEST_BAD) {
return;
}
// Change module status from Enabled to Disabled.
if ($view === 'form') {
$this->changeModuleStatusFromForm($module['module_name'], false, $expected);
}
else {
$this->changeModuleStatusFromPage($module['module_name'], 'Enabled');
}
// Check message and confirm that changes, made by the module, were reversed.
$message = ($view === 'form') ? 'Module updated' : 'Module disabled';
$this->assertMessage(TEST_GOOD, $message);
}
/**
* Function changes module status from the list in modules page.
*
* @param string $name module name
* @param string $current_status module current status that is going to be changed.
*/
private function changeModuleStatusFromPage($name, $current_status) {
$table = $this->query('class:list-table')->asTable()->one();
$row = $table->findRow('Name', $name);
$row->query('link', $current_status)->one()->click();
$this->page->waitUntilReady();
}
/**
* Function changes module status from the modules details form.
*
* @param string $name module name
* @param bool $enabled boolean value to be set in "Enabled" checkbox in module details form.
* @param constant $expected flag that determines whether the module update should succeed or fail.
*/
private function changeModuleStatusFromForm($name, $enabled, $expected) {
$this->query('link', $name)->waitUntilVisible()->one()->click();
$dialog = COverlayDialogElement::find()->one()->waitUntilReady();
// Edit module status and press update.
$dialog->query('id:status')->asCheckbox()->one()->set($enabled);
$this->query('button:Update')->one()->click();
if ($expected === TEST_GOOD) {
$dialog->ensureNotPresent();
}
}
}