<?php /* ** Zabbix ** Copyright (C) 2001-2025 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. **/ use Facebook\WebDriver\Exception\TimeoutException; require_once dirname(__FILE__).'/../include/CWebTest.php'; require_once dirname(__FILE__).'/behaviors/CTableBehavior.php'; /** * @backup hstgrp * * @onBefore prepareData */ class testPageSearch extends CWebTest { /** * Attach TableBehavior to the test. * * @return array */ public function getBehaviors() { return [CTableBehavior::class]; } protected $search_string = 'Test object'; protected static $widgets = [ 'hosts' => [ 'key' => 'hosts', 'selector' => 'id:search_hosts_widget', 'table_selector' => "xpath://div[@id='search_hosts_widget']//table", 'title' => 'Hosts', 'column_groups' => ['Host', 'IP', 'DNS', 'Monitoring', 'Configuration'], 'columns' => [ ['text' => 'Test object Host', 'href' => 'zabbix.php?action=host.edit&hostid={id}'], ['text' => '127.0.0.1'], ['text' => 'testdnstwo.example.com'], ['text' => 'Latest data', 'href' => 'zabbix.php?action=latest.view&hostids%5B%5D={id}&filter_set=1'], ['text' => 'Problems', 'href' => 'zabbix.php?action=problem.view&hostids%5B0%5D={id}&filter_set=1'], ['text' => 'Graphs', 'href' => 'zabbix.php?action=charts.view&filter_hostids%5B0%5D={id}&filter_set=1'], ['text' => 'Dashboards', 'href' => 'zabbix.php?action=host.dashboard.view&hostid={id}'], ['text' => 'Web', 'href' => 'zabbix.php?action=web.view&filter_hostids%5B%5D={id}&filter_set=1'], ['text' => 'Items', 'href' => 'items.php?filter_set=1&filter_hostids%5B0%5D={id}&context=host'], ['text' => 'Triggers', 'href' => 'triggers.php?filter_set=1&filter_hostids%5B0%5D={id}&context=host'], ['text' => 'Graphs', 'href' => 'graphs.php?filter_set=1&filter_hostids%5B0%5D={id}&context=host'], ['text' => 'Discovery', 'href' => 'host_discovery.php?filter_set=1&filter_hostids%5B0%5D={id}&context=host'], ['text' => 'Web', 'href' => 'httpconf.php?filter_set=1&filter_hostids%5B0%5D={id}&context=host'] ] ], 'hostgroups' => [ 'key' => 'host_groups', 'selector' => 'id:search_hostgroup_widget', 'table_selector' => "xpath://div[@id='search_hostgroup_widget']//table", 'title' => 'Host groups', 'column_groups' => ['Host group', 'Monitoring', 'Configuration'], 'columns' => [ ['text' => 'Test object Hostgroup', 'href' => 'hostgroups.php?form=update&groupid={id}&hostid=0'], ['text' => 'Latest data', 'href' => 'zabbix.php?action=latest.view&groupids%5B%5D={id}&filter_set=1'], ['text' => 'Problems', 'href' => 'zabbix.php?action=problem.view&groupids%5B0%5D={id}&filter_set=1'], ['text' => 'Web', 'href' => 'zabbix.php?action=web.view&filter_groupids%5B%5D={id}&filter_set=1'], ['text' => 'Hosts 1', 'href' => 'zabbix.php?action=host.list&filter_set=1&filter_groups%5B0%5D={id}'], ['text' => 'Templates 1', 'href' => 'templates.php?filter_set=1&filter_groups%5B0%5D={id}'] ] ], 'templates' => [ 'key' => 'templates', 'selector' => 'id:search_templates_widget', 'table_selector' => "xpath://div[@id='search_templates_widget']//table", 'title' => 'Templates', 'column_groups' => ['Template', 'Configuration'], 'columns' => [ ['text' => 'Test object Template', 'href' => 'templates.php?form=update&templateid={id}'], ['text' => 'Items', 'href' => 'items.php?filter_set=1&filter_hostids%5B0%5D={id}&context=template'], ['text' => 'Triggers', 'href' => 'triggers.php?filter_set=1&filter_hostids%5B0%5D={id}&context=template'], ['text' => 'Graphs', 'href' => 'graphs.php?filter_set=1&filter_hostids%5B0%5D={id}&context=template'], ['text' => 'Dashboards', 'href' => 'zabbix.php?action=template.dashboard.list&templateid={id}'], ['text' => 'Discovery', 'href' => 'host_discovery.php?filter_set=1&filter_hostids%5B0%5D={id}&context=template'], ['text' => 'Web', 'href' => 'httpconf.php?filter_set=1&filter_hostids%5B0%5D={id}&context=template'] ] ] ]; public function prepareData() { // This is needed so that all links in Search results are active. Also get IDs for checking links. $response = CDataHelper::call('hostgroup.create', [['name' => $this->search_string.' Hostgroup']]); self::$widgets['hostgroups']['link_id'] = $response['groupids'][0]; $response = CDataHelper::createHosts([ [ 'host' => 'emoji visible name', 'name' => '馃檪馃檭', 'groups' => ['groupid' => '6'], 'interfaces' => [ 'type' => 1, 'main' => 1, 'useip' => 1, 'ip' => '99.99.99.99', 'dns' => 'testdns.example.com', 'port' => '10050' ] ], [ 'host' => STRING_128, 'groups' => ['groupid' => '6'] ], [ 'host' => 'iGnoRe CaSe', 'name' => 'ZaBbiX 蟹袗袘斜袠泻褋 膩膿墨玫拧艞', 'groups' => ['groupid' => '6'] ], [ 'host' => $this->search_string.' Host', 'groups' => ['groupid' => self::$widgets['hostgroups']['link_id']], 'interfaces' => [ 'type' => 1, 'main' => 1, 'useip' => 1, 'ip' => '127.0.0.1', 'dns' => 'testdnstwo.example.com', 'port' => '10050' ] ], [ 'host' => 'Entities Host', 'groups' => ['groupid' => 6], 'items' => [ [ 'name' => 'Item 1', 'key_' => 'key[1]', 'type' => ITEM_TYPE_TRAPPER, 'value_type' => ITEM_VALUE_TYPE_UINT64 ], [ 'name' => 'Item 2', 'key_' => 'key[2]', 'type' => ITEM_TYPE_TRAPPER, 'value_type' => ITEM_VALUE_TYPE_UINT64 ] ], 'discoveryrules' => [ [ 'name' => 'Discovery 1', 'key_' => 'lld[1]', 'type' => ITEM_TYPE_TRAPPER ], [ 'name' => 'Discovery 2', 'key_' => 'lld[2]', 'type' => ITEM_TYPE_TRAPPER ] ] ] ]); self::$widgets['hosts']['link_id'] = $response['hostids'][$this->search_string.' Host']; $host_id = $response['hostids']['Entities Host']; $item_id = $response['itemids']['Entities Host:key[1]']; $response = CDataHelper::createTemplates([ [ 'host' => $this->search_string.' Template', 'groups' => ['groupid' => self::$widgets['hostgroups']['link_id']] ], [ 'host' => 'Entities Template', 'groups' => ['groupid' => 6], 'items' => [ [ 'name' => 'Item 1', 'key_' => 'key[1]', 'type' => ITEM_TYPE_TRAPPER, 'value_type' => ITEM_VALUE_TYPE_UINT64 ], [ 'name' => 'Item 2', 'key_' => 'key[2]', 'type' => ITEM_TYPE_TRAPPER, 'value_type' => ITEM_VALUE_TYPE_UINT64 ] ], 'discoveryrules' => [ [ 'name' => 'Discovery 1', 'key_' => 'lld[1]', 'type' => ITEM_TYPE_TRAPPER ], [ 'name' => 'Discovery 2', 'key_' => 'lld[2]', 'type' => ITEM_TYPE_TRAPPER ] ] ] ]); self::$widgets['templates']['link_id'] = $response['templateids'][$this->search_string.' Template']; $template_id = $response['templateids']['Entities Template']; $template_item_id = $response['itemids']['Entities Template:key[1]']; // Link graphs, web scenarios, triggers and dashboards to a host and a template. foreach ([$host_id => $item_id, $template_id => $template_item_id] as $parent_id => $item_id) { CDataHelper::call('graph.create', [ ['name' => 'Graph 1', 'gitems' => [['itemid' => $item_id, 'color' => '00FF00']]], ['name' => 'Graph 2', 'gitems' => [['itemid' => $item_id, 'color' => '00FF00']]] ]); CDataHelper::call('httptest.create', [ [ 'name' => 'Web 1', 'hostid' => $parent_id, 'steps' => [['name' => 'Step', 'url' => 'http://example.com', 'no' => 1]] ], [ 'name' => 'Web 2', 'hostid' => $parent_id, 'steps' => [['name' => 'Step', 'url' => 'http://example.com', 'no' => 1]] ] ]); } CDataHelper::call('trigger.create', [ ['description' => 'Trigger 1', 'expression' => 'last(/Entities Host/key[1])>1'], ['description' => 'Trigger 2', 'expression' => 'last(/Entities Host/key[1])>1'], ['description' => 'Trigger 1', 'expression' => 'last(/Entities Template/key[1])>1'], ['description' => 'Trigger 2', 'expression' => 'last(/Entities Template/key[1])>1'] ]); CDataHelper::call('templatedashboard.create', [ [ 'name' => 'Dashboard 1', 'templateid' => $template_id, 'pages' => [['widgets' => [['type' => 'clock']]]] ], [ 'name' => 'Dashboard 2', 'templateid' => $template_id, 'pages' => [['widgets' => [['type' => 'clock']]]] ] ]); // A host group and a template with no linked entities. CDataHelper::call('hostgroup.create', [['name' => 'Empty Hostgroup']]); CDataHelper::call('template.create', ['host' => 'Empty Template B', 'groups' => ['groupid' => 6]]); } /** * Check the layout of the Search form. */ public function testPageSearch_LayoutForm() { $this->page->login()->open('zabbix.php?action=dashboard.view'); $form = $this->query('class:form-search')->waitUntilVisible()->asForm()->one(); $search_field = $form->getField('id:search'); $search_button = $form->query('tag:button')->one(); $this->assertEquals(255, $search_field->getAttribute('maxlength')); $this->assertEquals('off', $search_field->getAttribute('autocomplete')); $this->assertFalse($search_button->isClickable()); $this->verifyThatSuggestionsNotShown(); // Check suggestion highlighting. $search_field->fill($this->search_string); $this->assertTrue($search_button->isClickable()); $highlighted_text = $this->query('class:suggest-found')->waitUntilVisible()->one()->getText(); $this->assertEquals(strtolower($this->search_string), strtolower($highlighted_text)); // Check that suggestions disappear after deleting input. $search_field->fill(''); $this->verifyThatSuggestionsNotShown(); $this->assertFalse($search_button->isClickable()); $search_field->fill($this->search_string); $search_button->waitUntilClickable()->click(); $this->page->assertHeader('Search: ' . $this->search_string); } /** * Check the layout of the Search result page. */ public function testPageSearch_LayoutPage() { $this->openSearchResults($this->search_string); $this->page->assertHeader('Search: '.$this->search_string); $this->page->assertTitle('Search'); // Assert result widget layout for each widget. foreach (self::$widgets as $widget_params) { $widget = $this->query($widget_params['selector'])->one(); $this->assertEquals($widget_params['title'], $widget->query('xpath:.//h4')->one()->getText()); $table = $widget->query('xpath:.//table')->asTable()->one(); // Check column group names. $this->assertEquals($widget_params['column_groups'], $table->getHeadersText()); // Check table links. $table_first_row = $table->getRow(0); foreach ($widget_params['columns'] as $i => $column) { // The same column name is sometimes used twice so need to access column by index. $column_element = $table_first_row->getColumn($i); // Assert text of the table field. $this->assertEquals($column['text'], $column_element->getText()); if (array_key_exists('href', $column)) { // Check that the link href matches. $expected_href = str_replace('{id}', $widget_params['link_id'], $column['href']); $this->assertEquals($expected_href, $column_element->query('tag:a')->one()->getAttribute('href')); } else { $this->assertFalse($column_element->isAttributePresent('href')); } } // Check expanding functionality. $widget_body = $widget->query('class:body')->one(); $collapse_button = $widget->query('class:btn-widget-collapse')->one(); $this->assertEquals('Collapse', $collapse_button->getAttribute('title')); $collapse_button->click(); $widget_body->waitUntilNotVisible(); $this->assertEquals('Expand', $collapse_button->getAttribute('title')); $expand_button = $widget->query('class:btn-widget-expand')->one(); $expand_button->click(); $widget_body->waitUntilVisible(); } } public static function getSearchData() { return [ [ [ 'search_string' => 'Non-existent host' ] ], [ [ 'search_string' => '袟袗袘袘袠袣小 小械褉胁械褉', 'hosts' => [['Host' => '袟袗袘袘袠袣小 小械褉胁械褉', 'IP' => '127.0.0.1', 'DNS' => '']] ] ], [ [ 'search_string' => 'Zabbix servers', 'host_groups' => [['Host group' => 'Zabbix servers']] ] ], [ [ 'search_string' => 'Form test template', 'templates' => [['Template' => 'Form test template']] ] ], [ [ 'search_string' => '馃檭', 'hosts' => [['Host' => '馃檪馃檭']], 'fire_keyup_event' => true ] ], [ [ 'search_string' => 'emoji visible name', 'hosts' => [['Host' => "馃檪馃檭\n(emoji visible name)"]] ] ], [ [ 'search_string' => 'ZABBIX 袟袗袘袘袠袣小 膧膾莫脮艩艝', 'hosts' => [['Host' => 'ZaBbiX 蟹袗袘斜袠泻褋 膩膿墨玫拧艞']] ] ], [ [ 'search_string' => 'ignore case', 'hosts' => [['Host' => "ZaBbiX 蟹袗袘斜袠泻褋 膩膿墨玫拧艞\n(iGnoRe CaSe)"]] ] ], [ [ 'search_string' => STRING_128, 'hosts' => [['Host' => STRING_128]] ] ], [ [ 'search_string' => 'a', 'count_from_db' => true ] ], [ [ 'search_string' => '99.99.99.99', 'hosts' => [['Host' => '馃檪馃檭', 'IP' => '99.99.99.99', 'DNS' => 'testdns.example.com']] ] ], [ [ 'search_string' => '127.0.0.1', 'count_from_db' => true ] ], [ [ 'search_string' => 'testdns.example.com', 'hosts' => [['Host' => '馃檪馃檭', 'IP' => '99.99.99.99', 'DNS' => 'testdns.example.com']] ] ], [ [ 'search_string' => 'st obj', 'hosts' => [['Host' => 'Test object Host']], 'host_groups' => [['Host group' => 'Test object Hostgroup']], 'templates' => [['Template' => 'Test object Template']] ] ] ]; } /** * Search for a string and verify the results. * * @dataProvider getSearchData */ public function testPageSearch_VerifyResults($data) { // Get expected result count from DB. if (CTestArrayHelper::get($data, 'count_from_db')) { $template_sql = 'SELECT NULL FROM hosts WHERE LOWER(host) LIKE '.zbx_dbstr('%'.$data['search_string'].'%').' AND status=3'; $hostgroup_sql = 'SELECT NULL FROM hstgrp WHERE LOWER(name) LIKE '.zbx_dbstr('%'.$data['search_string'].'%'); $host_sql = 'SELECT DISTINCT(h.host) FROM hosts h LEFT JOIN interface i on i.hostid=h.hostid'. ' WHERE h.status in (0,1) AND h.flags in (0,4)'. ' AND (LOWER(h.host) LIKE '.zbx_dbstr('%'.$data['search_string'].'%'). ' OR LOWER(h.name) LIKE '.zbx_dbstr('%'.$data['search_string'].'%'). ' OR i.dns LIKE '.zbx_dbstr('%'.$data['search_string'].'%'). ' OR i.ip LIKE '.zbx_dbstr('%'.$data['search_string'].'%').')'; $db_count = []; foreach (['hosts' => $host_sql, 'host_groups' => $hostgroup_sql, 'templates' => $template_sql] as $type => $sql) { $db_count[$type] = CDBHelper::getCount($sql); } } $this->openSearchResults($data['search_string'], CTestArrayHelper::get($data, 'fire_keyup_event')); $this->page->assertHeader('Search: '.$data['search_string']); // Verify each widget type. foreach (self::$widgets as $widget_params) { $widget = $this->query($widget_params['selector'])->one(); // Assert table data, but only if count from DB is not set. if (!CTestArrayHelper::get($data, 'count_from_db')) { $this->assertTableData(CTestArrayHelper::get($data, $widget_params['key'], []), $widget_params['table_selector']); } // Assert table stats. $expected_count = CTestArrayHelper::get($data, 'count_from_db') ? $db_count[$widget_params['key']] : (array_key_exists($widget_params['key'], $data) ? count($data[$widget_params['key']]) : 0); $footer_text = $widget->query('xpath:.//ul[@class="dashboard-widget-foot"]//li')->one()->getText(); // Only a maximum of 100 records are displayed at once. $this->assertEquals('Displaying '.(min($expected_count, 100)).' of '.$expected_count.' found', $footer_text); } } public static function getEntityData() { return [ [ [ 'search_string' => 'Test object Host', 'hosts' => [ 'Host' => ['count' => null], 'IP' => ['count' => null], 'DNS' => ['count' => null], 'Latest data' => ['count' => null, 'column_index' => 3], 'Problems' => ['count' => null, 'column_index' => 4], 'Graphs' => ['count' => null, 'column_index' => 5], 'Dashboards' => ['count' => null, 'column_index' => 6], 'Web' => ['count' => null, 'column_index' => 7], 'Items' => ['count' => null, 'column_index' => 8], 'Triggers' => ['count' => null, 'column_index' => 9], 'Graphs_2' => ['count' => null, 'column_index' => 10], 'Discovery' => ['count' => null, 'column_index' => 11], 'Web_2' => ['count' => null, 'column_index' => 12] ] ] ], [ [ 'search_string' => 'Entities Host', 'hosts' => [ 'Items' => ['count' => 2, 'column_index' => 8], 'Triggers' => ['count' => 2, 'column_index' => 9], 'Graphs' => ['count' => 2, 'column_index' => 10], 'Discovery' => ['count' => 2, 'column_index' => 11], 'Web' => ['count' => 2, 'column_index' => 12] ] ] ], [ [ 'search_string' => 'Empty Hostgroup', 'host_groups' => [ 'Host group' => ['count' => null], 'Latest data' => ['count' => null, 'column_index' => 1], 'Problems' => ['count' => null, 'column_index' => 2], 'Web' => ['count' => null, 'column_index' => 3], 'Hosts' => ['count' => null, 'column_index' => 4], 'Templates' => ['count' => null, 'column_index' => 5] ] ] ], [ [ 'search_string' => 'Test object Hostgroup', 'host_groups' => [ 'Hosts' => ['count' => 1, 'column_index' => 4], 'Templates' => ['count' => 1, 'column_index' => 5] ] ] ], [ [ 'search_string' => 'Empty Template B', 'templates' => [ 'Template' => ['count' => null], 'Items' => ['count' => null, 'column_index' => 1], 'Triggers' => ['count' => null, 'column_index' => 2], 'Graphs' => ['count' => null, 'column_index' => 3], 'Dashboards' => ['count' => null, 'column_index' => 4], 'Discovery' => ['count' => null, 'column_index' => 5], 'Web' => ['count' => null, 'column_index' => 6] ] ] ], [ [ 'search_string' => 'Entities Template', 'templates' => [ 'Items' => ['count' => 2, 'column_index' => 1], 'Triggers' => ['count' => 2, 'column_index' => 2], 'Graphs' => ['count' => 2, 'column_index' => 3], 'Dashboards' => ['count' => 2, 'column_index' => 4], 'Discovery' => ['count' => 2, 'column_index' => 5], 'Web' => ['count' => 2, 'column_index' => 6] ] ] ] ]; } /** * Search for a string and verify linked entity counts. * * @dataProvider getEntityData */ public function testPageSearch_VerifyEntityCount($data) { $this->openSearchResults($data['search_string']); // For each widget type. foreach (self::$widgets as $widget_params) { // Only check widget if any expected data is set for it. if (!array_key_exists($widget_params['key'], $data)) { continue; } $table_row = $this->query($widget_params['table_selector'])->asTable()->one()->getRow(0); // For each expected column. foreach ($data[$widget_params['key']] as $column_name => $column_data) { // Use column index when specified. This is because many columns are grouped. $column = $table_row->getColumn(CTestArrayHelper::get($column_data, 'column_index', $column_name)); if (CTestArrayHelper::get($column_data, 'count')) { $this->assertEquals($column_data['count'], $column->query('tag:sup')->one()->getText()); $this->assertFalse($column->isAttributePresent('href')); } else { // The text should not end with a space and a number. $this->assertEquals(0, preg_match('/ [0-9]+$/', $column->getText())); } } } } public static function getSuggestionsData() { return [ [ [ 'search_string' => 'Non-existent host', 'expected_suggestions' => [] ] ], [ [ 'search_string' => 'Test host', 'expected_suggestions' => [ 'Simple form test host', 'Template inheritance test host', 'Visible host for template linkage', '袟袗袘袘袠袣小 小械褉胁械褉' ] ] ], [ [ 'search_string' => 'a', 'expected_count' => 15 ] ], [ [ 'search_string' => ' ', 'expected_count' => 15 ] ], [ [ 'search_string' => '馃檭', 'expected_suggestions' => ['馃檪馃檭'], 'fire_keyup_event' => true ] ], [ [ 'search_string' => 'ignore case', 'expected_suggestions' => ['ZaBbiX 蟹袗袘斜袠泻褋 膩膿墨玫拧艞'] ] ], [ [ 'search_string' => 'ZABBIX 袟袗袘袘袠袣小 膧膾莫脮艩艝', 'expected_suggestions' => ['ZaBbiX 蟹袗袘斜袠泻褋 膩膿墨玫拧艞'] ] ], [ [ 'search_string' => STRING_128, 'expected_suggestions' => [STRING_128] ] ], [ [ 'search_string' => 'st obj', 'expected_suggestions' => ['Test object Host'] ] ] ]; } /** * Fill the Search input and verify that autocomplete shows the correct suggestions. * * @dataProvider getSuggestionsData */ public function testPageSearch_VerifySearchSuggestions($data) { $this->page->login()->open('zabbix.php?action=dashboard.view'); $form = $this->query('class:form-search')->waitUntilVisible()->asForm()->one(); $form->fill(['id:search' => $data['search_string']]); // Fill does not send a "keyup" event for non-standard strings, but it is needed to show the autocomplete window. if (CTestArrayHelper::get($data, 'fire_keyup_event')) { $form->getField('id:search')->fireEvent('keyup'); } $item_selector = 'xpath://ul[@class="search-suggest"]//li'; // Verify suggestions or the total count of suggestions. if (array_key_exists('expected_suggestions', $data)) { if (count($data['expected_suggestions']) > 0) { $items = $this->query($item_selector)->waitUntilVisible()->all()->asText(); $this->assertEquals($data['expected_suggestions'], array_values($items)); } else { $this->verifyThatSuggestionsNotShown(); } } else { $this->assertEquals($data['expected_count'], $this->query($item_selector)->waitUntilVisible()->all()->count()); } } /** * Test if the global search form is not being submitted with empty search string. */ public function testPageSearch_FindEmptyString() { $this->page->login()->open('zabbix.php?action=dashboard.view&dashboardid=1'); $form = $this->query('class:form-search')->waitUntilVisible()->asForm()->one(); foreach (['', ' '] as $search_string) { $form->query('id:search')->one()->fill($search_string); $form->submit(); $this->page->assertHeader('Global view'); } } /** * Verify that the suggestion list is NOT visible. */ protected function verifyThatSuggestionsNotShown() { try { $this->query('class:search-suggest')->waitUntilVisible(1); throw new Exception('Suggestions list shown when it should not be.'); } catch (TimeoutException $e) { // All good, the suggestion list is not visible, continue the test. } } /** * Opens Dashboard, enters search string and submits the search form. * * @param string $search_string text that will be entered in the search field */ protected function openSearchResults($search_string, $send_keyup = false) { $this->page->login()->open('zabbix.php?action=dashboard.view'); $form = $this->query('class:form-search')->waitUntilVisible()->asForm()->one(); $form->fill(['id:search' => $search_string]); // Fill does not send a "keyup" event for non-standard strings, but it is needed to enable the submit button. if ($send_keyup) { $form->getField('id:search')->fireEvent('keyup'); } $form->submit(); } }