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(); } } }