329 lines
12 KiB
PHP
329 lines
12 KiB
PHP
<?php
|
|
|
|
namespace LoretaAudit\Tests\DataSetup\Traits\App\Helpers;
|
|
|
|
trait formLayoutTrait
|
|
{
|
|
public function getFormElementSelector(string $id, string $selectorType = self::ID): string {
|
|
if ($selectorType !== self::ID) {
|
|
$this->fail("Only supports ID selector type for now.");
|
|
}
|
|
|
|
return ".doc .form-data #$id";
|
|
}
|
|
|
|
public function sendKeysAndTriggerValidation(string $selector, $value, string $selectorType = self::ID): void
|
|
{
|
|
// Hover the parent element
|
|
$containerXpath = $this->getFieldValidateContainerXpath($selector, $selectorType);
|
|
$this->elementWait($containerXpath, self::XPATH);
|
|
$this->scrollElementIntoView($containerXpath, self::XPATH);
|
|
$this->webDriver->action()->moveToElement($this->byXPath($containerXpath))->perform();
|
|
|
|
try {
|
|
// IMPORTANT: Pages can have multiple elements with the same id.
|
|
// Always type into the visible one.
|
|
$remoteElement = $this->getVisibleFormFieldElement($selector, $selectorType);
|
|
$classes = $remoteElement->getAttribute('class');
|
|
if (str_contains($classes, 'hasDatepicker') || str_contains($classes, 'chz')) {
|
|
/** Handle datepicker and dropdown differently */
|
|
if (str_contains($classes, 'hasDatepicker')) {
|
|
$this->setDatepickerValue($selector, $value, $selectorType);
|
|
}
|
|
|
|
if (str_contains($classes, 'chz')) {
|
|
$this->setDropdownValueWithJquery($selector, $value);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Clear & update field value
|
|
$remoteElement->click();
|
|
$remoteElement->clear()->sendKeys($value);
|
|
|
|
// Wait for field validation
|
|
$this->executeJSBlur();
|
|
} catch (\Exception $e) {
|
|
$msg = "\nFailed on element $selectorType: $selector";
|
|
$msg .= "\nContainer xpath: $containerXpath";
|
|
$msg .= "\nValue: $value";
|
|
$msg .= "\nMessage: {$e->getMessage()}";
|
|
$this->fail($msg);
|
|
}
|
|
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function setDatepickerValue(string $selector, $value, string $selectorType = self::ID): void
|
|
{
|
|
try {
|
|
$this->getRemoteWebElement($selector, $selectorType)->click()->clear()->sendKeys($value);
|
|
$this->executeJSBlur();
|
|
} catch (\Exception $e) {
|
|
$this->fail("Failed on setting Date value on: \nSelector: $selector \nValue: $value \nSelector type: $selectorType");
|
|
}
|
|
}
|
|
|
|
public function setDropdownValueWithJquery(string $id, $value): void
|
|
{
|
|
try {
|
|
// Trigger blur/change to ensure the chosen dropdown update
|
|
$this->executeJS("$('select#$id').val('$value').trigger('chosen:updated').trigger('change');");
|
|
$this->executeJSBlur();
|
|
$this->waitUntilDomReadyState();
|
|
} catch (\Exception $e) {
|
|
$this->fail("Failed on selecting dropdown value with jQuery on element id: $id");
|
|
}
|
|
}
|
|
|
|
public function setDropdownValueByIndexWithJquery(string $id, int $index): void
|
|
{
|
|
// eq(0) = first, eq(1) = second ...
|
|
$value = $this->executeJS("return $('select#$id').find('option').eq($index).val();");
|
|
$this->setDropdownValueWithJquery($id, $value);
|
|
}
|
|
|
|
/**
|
|
* Multi select box
|
|
*/
|
|
public function selectItemInMultiSelectBox(string $selector, string $value): void
|
|
{
|
|
/**
|
|
* This widget is a dual multi-select:
|
|
* - left: select#items_from
|
|
* - right: select#items_to
|
|
* - add: a.selector-add
|
|
* We select option(s) by value on the left and click "Add",
|
|
* then wait until the option appears on the right.
|
|
*/
|
|
try {
|
|
$this->openFormSection($selector);
|
|
$rootCss = "#$selector #multiselect_1";
|
|
$fromCss = "$rootCss select#items_from";
|
|
$toCss = "$rootCss select#items_to";
|
|
$addCss = "$rootCss a.selector-add";
|
|
|
|
$this->scrollElementIntoView($fromCss, self::CSS_SELECTOR);
|
|
$this->select($this->byCssSelector($fromCss))->selectByValue($value);
|
|
|
|
// Click "Add" (move to the right)
|
|
$this->scrollIntoViewHoverClick($addCss, self::CSS_SELECTOR);
|
|
|
|
// Wait until the moved option exists in the right select
|
|
$this->elementWait(
|
|
$toCss . ' option[value="' . $value . '"]',
|
|
self::CSS_SELECTOR,
|
|
true,
|
|
true,
|
|
false,
|
|
false
|
|
);
|
|
|
|
$this->executeJSBlur();
|
|
} catch (\Exception $e) {
|
|
$this->fail("Failed on selecting item in multi select box on element id: $selector");
|
|
}
|
|
}
|
|
|
|
public function removeItemInMultiSelectBox(string $selector, string $value): void
|
|
{
|
|
/** Remove a single value from the RIGHT box using "Remove". */
|
|
try {
|
|
$this->openFormSection($selector);
|
|
$rootCss = "#$selector #multiselect_1";
|
|
$toCss = "$rootCss select#items_to";
|
|
|
|
$this->scrollElementIntoView($toCss, self::CSS_SELECTOR);
|
|
$toSelect = $this->byCssSelector($toCss);
|
|
|
|
// Ensure the option is present before trying to remove (clear error message if not)
|
|
if (!$this->elementExists($toCss . ' option[value="' . $value . '"]', self::CSS_SELECTOR, true)) {
|
|
$this->fail("Value '$value' not found in selected multi select box.");
|
|
}
|
|
|
|
$this->select($toSelect)->selectByValue($value);
|
|
|
|
$this->scrollIntoViewHoverClick("$rootCss a.selector-remove", self::CSS_SELECTOR);
|
|
$this->executeJSBlur();
|
|
} catch (\Exception $e) {
|
|
$this->fail("Failed on removing item in multi select box on element id: $selector");
|
|
}
|
|
}
|
|
|
|
public function chooseAllInMultiSelectBox(string $selector): void
|
|
{
|
|
try {
|
|
$this->openFormSection($selector);
|
|
$this->scrollIntoViewHoverClick("#$selector #multiselect_1 .available a.chooseall", self::CSS_SELECTOR);
|
|
$this->executeJSBlur();
|
|
} catch (\Exception $e) {
|
|
$this->fail("Failed on choose-all in multi select box on element id: $selector");
|
|
}
|
|
}
|
|
|
|
public function clearAllInMultiSelectBox(string $selector): void
|
|
{
|
|
try {
|
|
$this->openFormSection($selector);
|
|
$this->scrollIntoViewHoverClick("#$selector #multiselect_1 .chosen a.clearall", self::CSS_SELECTOR);
|
|
$this->executeJSBlur();
|
|
} catch (\Exception $e) {
|
|
$this->fail("Failed on clear-all in multi select box on element id: $selector");
|
|
}
|
|
}
|
|
|
|
private function getMultiSelectChosenValues(string $selector): array
|
|
{
|
|
$selector = "#$selector #multiselect_1 select#items_to option";
|
|
if (!$this->elementExists($selector, self::CSS_SELECTOR, true)) {
|
|
return [];
|
|
}
|
|
|
|
$options = $this->getRemoteWebElements($selector, self::CSS_SELECTOR);
|
|
$values = [];
|
|
foreach ($options as $opt) {
|
|
$values[] = (string)$opt->getAttribute('value');
|
|
}
|
|
|
|
return $values;
|
|
}
|
|
|
|
public function assertMultiSelectChosenContains(string $selector, array $expected): void
|
|
{
|
|
$actual = $this->getMultiSelectChosenValues($selector);
|
|
$missing = array_values(array_diff($expected, $actual));
|
|
$this->assertEmpty(
|
|
$missing,
|
|
'Multiselect is missing expected values: ' . implode(', ', $missing) .
|
|
'. Actual: ' . implode(', ', $actual)
|
|
);
|
|
}
|
|
|
|
public function assertMultiSelectChosenEquals(string $selector, array $expected): void
|
|
{
|
|
$actual = $this->getMultiSelectChosenValues($selector);
|
|
$this->assertEquals(
|
|
$expected,
|
|
$actual,
|
|
'Multiselect chosen values mismatch. Expected: ' . implode(', ', $expected) .
|
|
'. Actual: ' . implode(', ', $actual)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Form Settings
|
|
*/
|
|
public function openFormSection(string $selector = 'psettings'): void
|
|
{
|
|
$settings = $this->byId($selector);
|
|
if (!strpos($settings->getAttribute('class'), 'hide')) {
|
|
$settings->click();
|
|
//$this->waitUntilDomReadyState();
|
|
}
|
|
}
|
|
|
|
public function closeFormSection(string $selector = 'psettings'): void
|
|
{
|
|
$settings = $this->byId($selector);
|
|
if (strpos($settings->getAttribute('class'), 'hide')) {
|
|
$settings->click();
|
|
//$this->waitUntilDomReadyState();
|
|
}
|
|
}
|
|
|
|
private function addNewDataRowInFormSection(string $settings)
|
|
{
|
|
$this->byCssSelector("#$settings > div > div.add_more > a")->click();
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
|
|
/**
|
|
* Form actions
|
|
*/
|
|
public function cancelMainForm(): void
|
|
{
|
|
$this->scrollIntoViewHoverClick('#base_table > div.doc div.title > div.fables-light-background-color > div > a', self::CSS_SELECTOR);
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function goBackToTable(): void
|
|
{
|
|
$this->scrollIntoViewHoverClick('.buttons_bottom #tback', self::CSS_SELECTOR);
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function saveMainForm(): void
|
|
{
|
|
$this->scrollIntoViewHoverClick('.buttons_bottom #fsave', self::CSS_SELECTOR);
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function saveMainFormAndAssertSuccess(string $lang): void
|
|
{
|
|
$this->saveMainForm();
|
|
$this->assertFormSuccessfullySavedMessage($lang);
|
|
}
|
|
|
|
|
|
/**
|
|
* Quick generate data for errors and save
|
|
*/
|
|
private function createNewDataRowsTestErrorsAndFillWithData(int $rows = 1, array $data = [], array $cfg = []): void
|
|
{
|
|
for ($row = 1; $row <= $rows; $row++) {
|
|
$this->createNewDataRowTestErrorsAndFillForSave($data, $cfg);
|
|
}
|
|
}
|
|
|
|
private function createNewDataRowTestErrorsAndFillForSave(array $testData, array $rowCfg = []): void
|
|
{
|
|
if (empty($testData) || empty($rowCfg)) {
|
|
return;
|
|
}
|
|
|
|
$elements = $this->getRemoteWebElements("{$rowCfg['container']} > ul", self::CSS_SELECTOR);
|
|
$this->byCssSelector($rowCfg['newRow'])->click();
|
|
$newIndex = (count($elements) + 1);
|
|
// Wait for the row to pop up
|
|
$this->elementWait($rowCfg['rowKey'] . $newIndex);
|
|
|
|
foreach ($testData as $field) {
|
|
|
|
// Change fields unique id in found a key
|
|
$field['v'] = str_replace('%ID%', $newIndex, $field['v']);
|
|
|
|
$id = $field['id'] . $newIndex;
|
|
$fieldPath = "{$rowCfg['inputPath']}#$id";
|
|
$fieldParentPath = str_replace('%FIELD%', $id, $rowCfg['inputXPath']);
|
|
$ribbonErrorSelector = "div.custom-status .cdrop [data-id='$id'] .emsg";
|
|
|
|
if ($field['mandatory'] ?? false) {
|
|
$this->webDriver->executeScript("$('$fieldPath').focus().blur();");
|
|
// Wait for the error
|
|
$this->elementWait("{$fieldParentPath}[contains(@class,'v-error')]", self::XPATH);
|
|
$this->elementWait($ribbonErrorSelector, self::CSS_SELECTOR);
|
|
|
|
// Check top ribbon error text
|
|
$actualText = $this->byCssSelector($ribbonErrorSelector)->getText();
|
|
$this->assertEquals(($field['o_text']), ($actualText));
|
|
|
|
$this->byCssSelector($fieldPath)->sendKeys($field['v']);
|
|
$this->webDriver->executeScript("$('$fieldPath').blur();");
|
|
$this->elementWaitRemoved("{$fieldParentPath}[contains(@class,'v-error')]", self::XPATH);
|
|
$this->elementWaitRemoved($ribbonErrorSelector, self::CSS_SELECTOR);
|
|
} elseif ($field['v'] ?? false) {
|
|
$this->byCssSelector($fieldPath)->clear();
|
|
$this->byCssSelector($fieldPath)->sendKeys($field['v']);
|
|
$this->webDriver->executeScript("$('$fieldPath').focus().blur();");
|
|
$this->elementWait("{$fieldParentPath}[contains(@class,'v-ok')]", self::XPATH);
|
|
$this->assertTrue(
|
|
$this->elementExists("{$fieldParentPath}[contains(@class,'v-ok')]", self::XPATH)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|