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.
587 lines
15 KiB
587 lines
15 KiB
<?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 'vendor/autoload.php';
|
|
|
|
require_once dirname(__FILE__).'/../CElement.php';
|
|
|
|
use Facebook\WebDriver\Remote\RemoteWebElement;
|
|
|
|
/**
|
|
* Form element.
|
|
*/
|
|
class CFormElement extends CElement {
|
|
|
|
const TABLE_FORM = 'ul[contains(@class, "table-forms")]';
|
|
const TABLE_FORM_LEFT = 'div[contains(@class, "table-forms-td-left")]';
|
|
const TABLE_FORM_RIGHT = 'div[contains(@class, "table-forms-td-right")]';
|
|
|
|
/**
|
|
* Local form input cache.
|
|
* @var array
|
|
*/
|
|
protected $fields;
|
|
|
|
/**
|
|
* Condition to be filtered by.
|
|
* @var CElementFilter
|
|
*/
|
|
protected $filter = null;
|
|
|
|
/**
|
|
* Class for required label.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $required_label = 'form-label-asterisk';
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public static function createInstance(RemoteWebElement $element, $options = []) {
|
|
$instance = parent::createInstance($element, $options);
|
|
|
|
if (!$instance->normalized && get_class($instance) !== CGridFormElement::class) {
|
|
$grid = $instance->query('xpath:.//div[contains(@class, "form-grid")]')->one(false);
|
|
if ($grid->isValid() && !$grid->parents('xpath:*[contains(@class, "table-forms-td-right")]')->exists()) {
|
|
return $instance->asGridForm($options);
|
|
}
|
|
}
|
|
|
|
return $instance;
|
|
}
|
|
|
|
/**
|
|
* Get filter.
|
|
*
|
|
* @return CElementFilter
|
|
*/
|
|
public function getFilter() {
|
|
return $this->filter;
|
|
}
|
|
|
|
/**
|
|
* Set filter conditions.
|
|
*
|
|
* @param mixed $filter conditions to be filtered by
|
|
* @param array $filter_params filter parameters
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setFilter($filter, $filter_params = []) {
|
|
if ($filter === null) {
|
|
$this->filter = null;
|
|
}
|
|
elseif ($filter instanceof CElementFilter) {
|
|
$this->filter = $filter;
|
|
}
|
|
else {
|
|
$this->filter = new CElementFilter($filter, $filter_params);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set filter for element collection.
|
|
*
|
|
* @param CElementCollection $elements collection of elements
|
|
* @param CElementFilter $filter condition to be filtered
|
|
* @param array $filter_params filter parameters
|
|
*
|
|
* @return CElementCollection
|
|
*/
|
|
protected function filterCollection($elements, $filter, $filter_params = []) {
|
|
if ($this->filter !== null) {
|
|
$elements = $elements->filter($this->filter);
|
|
}
|
|
|
|
if ($filter !== null) {
|
|
$elements = $elements->filter($filter, $filter_params);
|
|
}
|
|
|
|
return $elements;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
protected function setElement(RemoteWebElement $element) {
|
|
parent::setElement($element);
|
|
$this->invalidate();
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
protected function normalize() {
|
|
if ($this->getTagName() !== 'form') {
|
|
$this->setElement($this->query('xpath:.//form')->waitUntilPresent()->one());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function invalidate() {
|
|
parent::invalidate();
|
|
|
|
$this->fields = new CElementCollection([]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get collection of form label elements.
|
|
*
|
|
* @param CElementFilter $filter condition to be filtered
|
|
* @param array $filter_params filter parameters
|
|
*
|
|
* @return CElementCollection
|
|
*/
|
|
public function getLabels($filter = null, $filter_params = []) {
|
|
$labels = $this->query('xpath:.//'.self::TABLE_FORM.'/li/'.self::TABLE_FORM_LEFT.'/label')->all();
|
|
|
|
foreach ($labels as $key => $label) {
|
|
if ($label->getText() === '') {
|
|
$element = $this->getFieldByLabelElement($label);
|
|
if ($element->isValid() && get_class($element) === CCheckboxElement::class) {
|
|
$labels->set($key, $element->query('xpath:../label')->one(false));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->filterCollection($labels, $filter, $filter_params);
|
|
}
|
|
|
|
/**
|
|
* Get form label element by text.
|
|
*
|
|
* @param string $name field label text
|
|
*
|
|
* @return CElement
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function getLabel($name) {
|
|
$labels = $this->findLabels($name);
|
|
|
|
if ($labels->isEmpty()) {
|
|
throw new Exception('Failed to find form label by name: "'.$name.'".');
|
|
}
|
|
|
|
if ($labels->count() > 1) {
|
|
$labels = $labels->filter(new CElementFilter(CElementFilter::VISIBLE));
|
|
|
|
if ($labels->isEmpty()) {
|
|
throw new Exception('Failed to find visible form label by name: "'.$name.'".');
|
|
}
|
|
|
|
if ($labels->count() > 1) {
|
|
CTest::zbxAddWarning('Form label "'.$name.'" is not unique.');
|
|
}
|
|
}
|
|
|
|
return $labels->first();
|
|
}
|
|
|
|
/**
|
|
* Get label elements by text.
|
|
*
|
|
* @param string $name field label text
|
|
*
|
|
* @return CElementCollection
|
|
*/
|
|
protected function findLabels($name) {
|
|
$prefix = 'xpath:.//'.self::TABLE_FORM.'/li/'.self::TABLE_FORM_LEFT;
|
|
return $this->query($prefix.'/label[text()='.CXPathHelper::escapeQuotes($name).']')->all();
|
|
}
|
|
|
|
/**
|
|
* Get element field by label element.
|
|
*
|
|
* @param CElement $label label element
|
|
*
|
|
* @return CElement|CNullElement
|
|
*/
|
|
public function getFieldByLabelElement($label) {
|
|
if (($element = CElementQuery::getInputElement($label, './../../'.self::TABLE_FORM_RIGHT))->isValid()) {
|
|
return $element;
|
|
}
|
|
|
|
// Nested table forms.
|
|
$element = $label->query('xpath', './../../'.self::TABLE_FORM_RIGHT.'//'.self::TABLE_FORM.'/..')
|
|
->cast('CFormElement', ['normalized' => true])
|
|
->one(false);
|
|
|
|
if (!$element->isValid()) {
|
|
$element = $label->query('xpath:./../../'.self::TABLE_FORM_RIGHT)->one(false);
|
|
}
|
|
|
|
return $element;
|
|
}
|
|
|
|
/**
|
|
* Get collection of element fields indexed by label name.
|
|
*
|
|
* @param CElementFilter $filter condition to be filtered by
|
|
* @param array $filter_params condition parameters to be set
|
|
*
|
|
* @return CElementCollection
|
|
*/
|
|
public function getFields($filter = null, $filter_params = []) {
|
|
$fields = [];
|
|
|
|
foreach ($this->getLabels() as $label) {
|
|
$element = $this->getFieldByLabelElement($label);
|
|
|
|
if ($element->isValid()) {
|
|
$fields[$label->getText()] = $element;
|
|
}
|
|
}
|
|
|
|
$this->fields = $this->filterCollection(new CElementCollection($fields), $filter, $filter_params);
|
|
|
|
return $this->fields;
|
|
}
|
|
|
|
/**
|
|
* Get field by label name or selector.
|
|
*
|
|
* @param string $name field label text or element selector
|
|
* @param boolean $invalidate cache usage flag
|
|
*
|
|
* @return CElement
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function getField($name, $invalidate = false) {
|
|
if (!$invalidate && $this->fields->exists($name)) {
|
|
return $this->fields->get($name);
|
|
}
|
|
|
|
$parts = explode(':', $name, 2);
|
|
if (count($parts) === 2 && in_array($parts[0], ['id', 'name', 'css', 'class', 'tag', 'link', 'button', 'xpath'])
|
|
&& ($element = $this->query($name)->one(false))->isValid()) {
|
|
$element = $element->detect();
|
|
}
|
|
else {
|
|
try {
|
|
$label = $this->getLabel($name);
|
|
$element = $this->getFieldByLabelElement($label);
|
|
}
|
|
catch (\Exception $exception) {
|
|
$label = $this->query('xpath:.//label[text()='.CXPathHelper::escapeQuotes($name).']')->one(false);
|
|
if (!$label->isValid()) {
|
|
throw $exception;
|
|
}
|
|
|
|
$element = CElementQuery::getInputElement($label, './../');
|
|
if (get_class($element) !== CCheckboxElement::class) {
|
|
throw $exception;
|
|
}
|
|
}
|
|
|
|
if (!$element->isValid()) {
|
|
throw new Exception('Failed to find form field by label name or selector: "'.$name.'".');
|
|
}
|
|
}
|
|
|
|
$this->fields->set($name, $element);
|
|
|
|
return $element;
|
|
}
|
|
|
|
/**
|
|
* Get field container element by label name.
|
|
*
|
|
* @param string $name field label text
|
|
*
|
|
* @return CElement
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function getFieldContainer($name) {
|
|
return $this->getLabel($name)->query('xpath:./../../'.self::TABLE_FORM_RIGHT)->one();
|
|
}
|
|
|
|
/**
|
|
* Get tabs from form.
|
|
*
|
|
* @return CElementCollection
|
|
*/
|
|
public function getTabs() {
|
|
return $this->query("xpath:.//li[@role='tab']")->all()->asText();
|
|
}
|
|
|
|
/**
|
|
* Switch to tab by tab name.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function selectTab($name) {
|
|
$selector = '/a[text()='.CXPathHelper::escapeQuotes($name).']';
|
|
|
|
$this->query('xpath:.//ul[contains(@class, "ui-tabs-nav")]/'.$selector)->waitUntilPresent()->one()->click();
|
|
$this->query('xpath:.//li[@aria-selected="true"]'.$selector)->waitUntilPresent();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get text of selected tab.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getSelectedTab() {
|
|
return $this->query('xpath:.//ul[contains(@class, "ui-tabs-nav")]'.
|
|
'//li[@aria-selected="true"]/a')->waitUntilPresent()->one()->getText();
|
|
}
|
|
|
|
/**
|
|
* Get message of form in overlay dialog.
|
|
*
|
|
* @return CMessageElement
|
|
*/
|
|
public function getOverlayMessage() {
|
|
return $this->parents('class:overlay-dialogue-body')->waitUntilVisible()->one()
|
|
->query('tag:output')->asMessage()->waitUntilVisible()->one();
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function submit() {
|
|
$buttons = $this->query('xpath:.//button[@type="submit"]|.//input[@type="submit"]')->all();
|
|
|
|
if ($buttons->count() > 1) {
|
|
$buttons = $buttons->filter(new CElementFilter(CElementFilter::VISIBLE));
|
|
}
|
|
|
|
$submit = ($buttons->count() === 0) ? (new CNullElement()) : $buttons->first();
|
|
|
|
if ($submit->isValid()) {
|
|
$submit->click(true);
|
|
}
|
|
else {
|
|
parent::submit();
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Fill form fields with specific values.
|
|
*
|
|
* @param string $field field name to filled in
|
|
* @param string $values value to be put in field
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function setFieldValue($field, $values) {
|
|
$classes = [CMultifieldTableElement::class, CMultiselectElement::class, CCheckboxListElement::class, CHostInterfaceElement::class];
|
|
$element = $this->getField($field);
|
|
|
|
if (is_array($values) && !in_array(get_class($element), $classes)) {
|
|
if ($values !== []) {
|
|
$container = $this->getFieldContainer($field);
|
|
if ($this->filter !== null && !$this->filter->match($container)) {
|
|
return $this;
|
|
}
|
|
|
|
foreach ($values as $name => $value) {
|
|
$xpath = './/*[@id='.CXPathHelper::escapeQuotes($name).' or @name='.CXPathHelper::escapeQuotes($name).']';
|
|
$container->query('xpath', $xpath)->one()->detect()->fill($value);
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
elseif ($values instanceof \Closure) {
|
|
$values($this, $field, $element);
|
|
|
|
return $this;
|
|
}
|
|
|
|
if ($this->filter !== null && !$this->filter->match($element)) {
|
|
return $this;
|
|
}
|
|
|
|
$element->fill($values);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Fill form with specified data.
|
|
*
|
|
* @param array $data data array where keys are label text and values are values to be put in fields
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function fill($data) {
|
|
if ($data && is_array($data)) {
|
|
foreach ($data as $field => $values) {
|
|
try {
|
|
$this->setFieldValue($field, $values);
|
|
}
|
|
catch (\Exception $e1) {
|
|
sleep(1);
|
|
|
|
try {
|
|
$this->invalidate();
|
|
$this->setFieldValue($field, $values);
|
|
} catch (\Exception $e2) {
|
|
throw $e1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function checkValue($expected, $raise_exception = true) {
|
|
if ($expected && is_array($expected)) {
|
|
foreach ($expected as $field => $value) {
|
|
if ($value instanceof \Closure) {
|
|
$function = new ReflectionFunction($value);
|
|
$variables = $function->getStaticVariables();
|
|
$value = $variables['value'];
|
|
}
|
|
|
|
if ($this->checkFieldValue($field, $value, $raise_exception) === false) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check single form field value to have a specific value.
|
|
*
|
|
* @param string $field field name to filled checked
|
|
* @param mixed $values value to be checked in field
|
|
* @param boolean $raise_exception flag to raise exceptions on error
|
|
*
|
|
* @return boolean
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
protected function checkFieldValue($field, $values, $raise_exception = true) {
|
|
$classes = [CMultifieldTableElement::class, CMultiselectElement::class, CCheckboxListElement::class, CHostInterfaceElement::class];
|
|
$element = $this->getField($field);
|
|
|
|
if (is_array($values) && !in_array(get_class($element), $classes)) {
|
|
if ($values !== []) {
|
|
$container = $this->getFieldContainer($field);
|
|
if ($this->filter !== null && !$this->filter->match($container)) {
|
|
if ($raise_exception) {
|
|
throw new Exception('Failed to check value of field not matching the filter.');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
foreach ($values as $name => $value) {
|
|
$xpath = './/*[@id='.CXPathHelper::escapeQuotes($name).' or @name='.CXPathHelper::escapeQuotes($name).']';
|
|
if (!$container->query('xpath', $xpath)->one()->detect()->checkValue($value, $raise_exception)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if ($this->filter !== null && !$this->filter->match($element)) {
|
|
if ($raise_exception) {
|
|
throw new Exception('Failed to check value of field not matching the filter.');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return $element->checkValue($values, $raise_exception);
|
|
}
|
|
catch (\Exception $exception) {
|
|
CExceptionHelper::setMessage($exception, 'Failed to check value of field "'.$field.'":' . "\n" . $exception->getMessage());
|
|
throw $exception;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for form reload after form element select.
|
|
*
|
|
* @param string $value text to be written into the field
|
|
*
|
|
* @return Closure
|
|
*/
|
|
public static function RELOADABLE_FILL($value) {
|
|
return function ($form, $field, $element) use ($value) {
|
|
if (!($element instanceof CDropdownElement) || $element->getText() !== $value) {
|
|
$element->fill($value);
|
|
$form->waitUntilReloaded();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if field is marked as required in form.
|
|
*
|
|
* @param string $label field label text
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isRequired($label) {
|
|
return $this->getLabel($label)->hasClass($this->required_label);
|
|
}
|
|
|
|
/**
|
|
* Get the mandatory labels marked with an asterisk.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getRequiredLabels() {
|
|
$labels = $this->getLabels(CElementFilter::CLASSES_PRESENT, [$this->required_label])
|
|
->filter(CElementFilter::VISIBLE)->asText();
|
|
|
|
return array_values($labels);
|
|
}
|
|
|
|
/**
|
|
* Get form fields values.
|
|
*
|
|
* @param CElementFilter $filter condition to be filtered by
|
|
* @param array $filter_params condition parameters to be set
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getValues($filter = null, $filter_params = []) {
|
|
return $this->getFields($filter, $filter_params)->asValues();
|
|
}
|
|
}
|