ElixirVault/tests/DataSetup/Traits/App/Helpers/formLayoutTrait.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)
);
}
}
}
}