566 lines
20 KiB
PHP
566 lines
20 KiB
PHP
<?php
|
|
|
|
namespace LoretaAudit\Tests\DataSetup\Traits\App\Helpers;
|
|
|
|
use Facebook\WebDriver\Exception\WebDriverException;
|
|
use Facebook\WebDriver\WebDriverBy;
|
|
use Facebook\WebDriver\WebDriverKeys;
|
|
|
|
trait tableLayoutTrait
|
|
{
|
|
private $baseTableData = '#base_table > div.table-data';
|
|
private $colHeaderSelector = '#cms-table-drop-marker ul.thead';
|
|
private $tableBodyContainer = '#cms-table-drop-marker .table-rows .tbody';
|
|
private $tablePagerContainer = "//ul[@class='dx pager-cnt']";
|
|
|
|
public function testTableHeaderTitles(array $idToTitleText = null): void
|
|
{
|
|
if (is_null($idToTitleText)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($idToTitleText as $id => $textToValidate) {
|
|
|
|
$this->assertTrue(
|
|
$this->elementExists("$this->colHeaderSelector #$id", self::CSS_SELECTOR),
|
|
"Failed locating element by CSS selector: $this->colHeaderSelector #$id"
|
|
);
|
|
|
|
switch ($id) {
|
|
case 'h_checkbox':
|
|
break;
|
|
|
|
case 'h_buttons':
|
|
$this->assertStringContainsString(
|
|
$textToValidate,
|
|
$this->byCssSelector("$this->colHeaderSelector #$id > a")->getText()
|
|
);
|
|
break;
|
|
|
|
default:
|
|
$this->assertEquals(
|
|
$textToValidate,
|
|
$this->byCssSelector("$this->colHeaderSelector #$id div")->getText()
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function testTableRowBaseCheckboxes(): void
|
|
{}
|
|
|
|
public function testTableBaseFunctionality()
|
|
{
|
|
// Test header columns
|
|
|
|
// Test column sort
|
|
|
|
// Test column reorder
|
|
|
|
// Test rows per page
|
|
|
|
// Test column visibility
|
|
|
|
// Test row search
|
|
|
|
}
|
|
|
|
|
|
/** Table settings */
|
|
public function openTableSettings(string $id = 'iTableSettings', string $class = 'base-table-settings'): void
|
|
{
|
|
$headerSelector = "#dialogSlider.$class .slider-wrapper .header > h3";
|
|
if (
|
|
$this->elementExists($headerSelector, self::CSS_SELECTOR, true) &&
|
|
$this->byCssSelector($headerSelector)->isDisplayed()
|
|
) {
|
|
return;
|
|
}
|
|
|
|
$this->selectTableActionById($id);
|
|
$this->waitTableSlider($headerSelector);
|
|
}
|
|
|
|
public function closeTableSettings(): void
|
|
{
|
|
$headerSelector = '#dialogSlider .slider-wrapper .header > h3';
|
|
if (
|
|
$this->elementExists($headerSelector, self::CSS_SELECTOR) &&
|
|
$this->byCssSelector($headerSelector)->isDisplayed()
|
|
) {
|
|
$this->scrollElementIntoView($headerSelector, self::CSS_SELECTOR);
|
|
$this->byCssSelector("$headerSelector a.close")->click();
|
|
$this->elementWaitRemoved("$headerSelector a.close", self::CSS_SELECTOR);
|
|
}
|
|
}
|
|
|
|
public function updateTableSettingsViewMaxRows(int $rows = 40): void
|
|
{
|
|
$this->openTableSettings();
|
|
$this->setTableSettingsMaxRowsPerPage($rows);
|
|
$this->cmsConfirmDialogSliderActionYes();
|
|
|
|
$this->waitUntilDomReadyState();
|
|
$rowsListed = $this->getRemoteWebElements("$this->tableBodyContainer > ul", self::CSS_SELECTOR);
|
|
$this->assertTrue(count($rowsListed) <= $rows);
|
|
}
|
|
|
|
public function setTableSettingsMaxRowsPerPage(int $rowsPerPage = 10): void
|
|
{
|
|
$this->openTableSettings();
|
|
$inputSelector = '#dialogSlider .slider-wrapper #pBase #a_rec_per_page';
|
|
$this->scrollElementIntoView($inputSelector, self::CSS_SELECTOR);
|
|
|
|
$this->byCssSelector($inputSelector)->clear()->sendKeys($rowsPerPage);
|
|
$this->webDriver->executeScript('$("' . $inputSelector . '").focus().blur();');
|
|
}
|
|
|
|
public function selectTableSettingsCheckbox(string $id, bool $visibility): void
|
|
{
|
|
$chkSelector = "#dialogSlider .slider-wrapper .document-settings .inner-cnt > ul #$id";
|
|
$this->scrollElementIntoView($chkSelector, self::CSS_SELECTOR);
|
|
|
|
$element = $this->byCssSelector($chkSelector);
|
|
$isChecked = str_contains($element->getAttribute('class'), "chked");
|
|
|
|
if ($isChecked !== $visibility) {
|
|
$element->click();
|
|
}
|
|
}
|
|
|
|
public function selectTableRow(int $row): void
|
|
{
|
|
$chkSelector = $this->tableBodyContainer . " > ul:nth-child($row) > li.first > div > div.chk";
|
|
$this->toggleCheckbox($chkSelector);
|
|
}
|
|
|
|
public function resetTableSettingsColumnConfig(array $defaultConfig): void
|
|
{
|
|
$this->openTableSettings();
|
|
$this->scrollIntoViewHoverClick("#dialogSlider .slider-wrapper #reset_cfg", self::CSS_SELECTOR);
|
|
$this->waitUntilDomReadyState();
|
|
$this->elementWaitRemoved("#dialogSlider .slider-wrapper a.close", self::CSS_SELECTOR);
|
|
|
|
$this->testTableHeaderTitles($defaultConfig);
|
|
}
|
|
|
|
public function updateTableSettingsColumnsVisibility(array $config): void
|
|
{
|
|
$this->openTableSettings();
|
|
|
|
foreach ($config as $id => $data) {
|
|
$this->selectTableSettingsCheckbox($id, $data);
|
|
}
|
|
|
|
$this->cmsConfirmDialogSliderActionYes();
|
|
$this->waitUntilDomReadyState();
|
|
|
|
foreach ($config as $id => $visibility) {
|
|
$elementExists = $this->elementExists("$this->colHeaderSelector > #$id", self::CSS_SELECTOR, true);
|
|
$this->assertEquals($visibility, $elementExists, "Element id: $id");
|
|
}
|
|
}
|
|
|
|
public function reorderTableSettingsColumnConfig(array $orderedIds): void
|
|
{
|
|
$this->openTableSettings();
|
|
|
|
$allItemsSel = '#dialogSlider .slider-wrapper .document-settings .inner-cnt li';
|
|
$allItems = $this->getRemoteWebElements($allItemsSel, self::CSS_SELECTOR);
|
|
|
|
foreach ($orderedIds as $targetPosition => $itemId) {
|
|
$elementXpath = "//div[@id='dialogSlider']/div[2]//ul/li[.//*[@id='$itemId']]";
|
|
$targetPosition += 1; // First is reserved
|
|
$this->dragTableSettingListItemToPosition($elementXpath, $allItems, $targetPosition);
|
|
}
|
|
|
|
$this->cmsConfirmDialogSliderActionYes();
|
|
|
|
foreach ($orderedIds as $itemId) {
|
|
$exists = $this->elementExists("$this->colHeaderSelector > #$itemId", self::CSS_SELECTOR, true);
|
|
$this->assertTrue($exists, 'Failed on: ' . $itemId);
|
|
}
|
|
}
|
|
|
|
public function reorderTableColumn(array $orderedIds): void
|
|
{
|
|
foreach ($orderedIds as $targetPosition => $itemId) {
|
|
$elementXpath = "//*[@id='cms-table-drop-marker']//li[@id='$itemId']";
|
|
$allItemsSel = $this->colHeaderSelector . ' > li';
|
|
$allItems = $this->getRemoteWebElements($allItemsSel, self::CSS_SELECTOR);
|
|
$targetPosition += 2;
|
|
$this->dragTableSettingListItemToPosition($elementXpath, $allItems, $targetPosition);
|
|
}
|
|
|
|
foreach ($orderedIds as $itemId) {
|
|
$exists = $this->elementExists("$this->colHeaderSelector > #$itemId", self::CSS_SELECTOR, true);
|
|
$this->assertTrue($exists, 'Failed on: ' . $itemId);
|
|
}
|
|
}
|
|
|
|
public function dragTableSettingListItemToPosition(string $xpath, array $allItems, int $targetPosition): void
|
|
{
|
|
$sourceElement = $this->scrollElementIntoView($xpath, self::XPATH);
|
|
$currentPosition = $this->getElementPosition($sourceElement, $allItems);
|
|
if ($currentPosition === $targetPosition) {
|
|
return;
|
|
}
|
|
|
|
$targetElement = $allItems[$targetPosition];
|
|
$movingLeft = $currentPosition > $targetPosition;
|
|
|
|
// Target near edge of the target element — avoids misfiring on wide columns
|
|
// Offset is from the center: negative = left side, positive = right side
|
|
$targetWidth = $targetElement->getSize()->getWidth();
|
|
$xOffset = $movingLeft
|
|
? -intdiv($targetWidth, 2) + 5 // near left edge
|
|
: intdiv($targetWidth, 2) - 5; // near right edge
|
|
|
|
try {
|
|
$this->webDriver->action()
|
|
->clickAndHold($sourceElement)
|
|
->moveToElement($targetElement, $xOffset, 0)
|
|
->release()
|
|
->perform();
|
|
$this->waitUntilDomReadyState();
|
|
} catch (WebDriverException $e) {
|
|
$this->assertFalse(
|
|
sprintf(
|
|
"dragListItemToPosition failed.\nSource xpath: %s\nError: %s",
|
|
$xpath,
|
|
$e->getMessage()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public function waitTableSlider($headerSelector): void
|
|
{
|
|
$this->elementWait($headerSelector, self::CSS_SELECTOR);
|
|
$this->scrollElementIntoView($headerSelector, self::CSS_SELECTOR);
|
|
|
|
$exists = $this->elementExists($headerSelector, self::CSS_SELECTOR, true);
|
|
$this->assertTrue($exists, "Failed locating element by CSS selector: $headerSelector");
|
|
}
|
|
|
|
|
|
/** Table actions */
|
|
public function selectTableActionById($id): void
|
|
{
|
|
$this->scrollIntoViewHoverClick("$this->colHeaderSelector #h_buttons a", self::CSS_SELECTOR);
|
|
$this->scrollIntoViewHoverClick("#table-actions li#$id a", self::CSS_SELECTOR);
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function deleteTableRow($row, bool $scrollIntoView = false): void
|
|
{
|
|
$selector = $this->tableBodyContainer . " > ul:nth-child($row) > li.last.actions > div > a#ldelete";
|
|
$this->openConfirmDialog($selector, $scrollIntoView);
|
|
$this->cmsConfirmDialogActionYes();
|
|
}
|
|
|
|
public function editTableRow($row, bool $scrollIntoView = false): void
|
|
{
|
|
$selector = $this->tableBodyContainer . " > ul:nth-child($row) > li.last.actions > div > a#ledit";
|
|
|
|
if ($scrollIntoView) {
|
|
$this->scrollElementIntoView($selector, self::CSS_SELECTOR);
|
|
}
|
|
|
|
$this->byCssSelector($selector)->click();
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function editTableRowById(int $id, bool $scrollIntoView = false): void
|
|
{
|
|
$selector = $this->tableBodyContainer . " ul[id='$id'] > li.last.actions > div > a#ledit";
|
|
|
|
if ($scrollIntoView) {
|
|
$this->scrollElementIntoView($selector, self::CSS_SELECTOR);
|
|
}
|
|
$this->byCssSelector($selector)->click();
|
|
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function clickRowExcelExport($row, bool $scrollIntoView = false): void
|
|
{
|
|
$selector = $this->tableBodyContainer . " > ul:nth-child($row) > li > div > a.download_pdf";
|
|
|
|
if ($scrollIntoView) {
|
|
$this->scrollElementIntoView($selector, self::CSS_SELECTOR);
|
|
}
|
|
$this->byCssSelector($selector)->click();
|
|
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function clickRowExcelExportById(int $id, bool $scrollIntoView = false): void
|
|
{
|
|
$selector = $this->tableBodyContainer . " > ul[id='$id'] > li > div > a.download_pdf";
|
|
if ($scrollIntoView) {
|
|
$this->scrollElementIntoView($selector, self::CSS_SELECTOR);
|
|
}
|
|
|
|
$this->byCssSelector($selector)->click();
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function addNewRecord(): void
|
|
{
|
|
$this->scrollIntoViewHoverClick('.table-bactions ' . $this->getNewButtonId(), self::CSS_SELECTOR);
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function getNewButtonId(): string
|
|
{
|
|
return '#bButtonNew';
|
|
}
|
|
|
|
public function cloneTableRow(int $row): void
|
|
{
|
|
$this->selectTableRow($row);
|
|
$this->byId('clone')->click();
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
|
|
/** Table search */
|
|
public function openCmsTableSearch(): void
|
|
{
|
|
$this->byId("btnSearch")->click();
|
|
}
|
|
|
|
public function resetCmsTableSearch(): void
|
|
{
|
|
$this->byCssSelector(".sticky-header .search-hidden #tSearch > ul #tclear")->click();
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
|
|
public function searchCmsTableByColumn(string $columnID, string $value, bool $submit = true): void
|
|
{
|
|
if (!$this->getRemoteWebElement('tSearch')->isDisplayed()) {
|
|
$this->openCmsTableSearch();
|
|
}
|
|
|
|
$search = ".sticky-header .search-hidden #tSearch > ul";
|
|
$selector = "$search #$columnID";
|
|
$parentLi = $this->byCssSelector($selector)->findElement(WebDriverBy::xpath("./ancestor::li[1]"));
|
|
if (strpos($parentLi->getAttribute('class'), 'dropBox') !== false) {
|
|
$this->scrollIntoViewHoverClick("//input[@id='$columnID']/preceding-sibling::span[1]", self::XPATH);
|
|
$selectedValue = "//input[@id='$columnID']/ancestor::li//ul/li[@id='$value']";
|
|
$this->scrollIntoViewHoverClick($selectedValue, self::XPATH);
|
|
$this->waitUntilDomReadyState();
|
|
// We don't need to submit the search as DropBox auto submits the form.
|
|
} else {
|
|
$element = $this->byCssSelector($selector);
|
|
$element->click()->clear()->sendKeys($value);
|
|
if ($submit) {
|
|
$element->click()->sendKeys(WebDriverKeys::ENTER);
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
}
|
|
}
|
|
|
|
public function sortTableByColumn(string $columnID, string $orderWay = 'ASC'): void
|
|
{
|
|
$colSelector = "$this->colHeaderSelector #$columnID";
|
|
$this->assertTrue(
|
|
$this->elementExists($colSelector, self::CSS_SELECTOR),
|
|
"Failed locating element by CSS selector: $this->colHeaderSelector #$columnID"
|
|
);
|
|
|
|
if (
|
|
$orderWay == 'ASC' &&
|
|
(
|
|
$this->elementHasClass('sortDesc', $colSelector, self::CSS_SELECTOR) ||
|
|
!$this->elementHasClass('sort', $colSelector, self::CSS_SELECTOR)
|
|
)
|
|
) {
|
|
$this->byCssSelector($colSelector)->click();
|
|
$this->elementWait("$this->colHeaderSelector #$columnID.sort", self::CSS_SELECTOR);
|
|
}
|
|
|
|
if (
|
|
$orderWay == 'DESC' &&
|
|
!$this->elementHasClass('sortDesc', $colSelector, self::CSS_SELECTOR)
|
|
) {
|
|
if (!$this->elementHasClass('sort', $colSelector, self::CSS_SELECTOR)) {
|
|
$this->byCssSelector($colSelector)->click();
|
|
$this->elementWait("$this->colHeaderSelector #$columnID.sort", self::CSS_SELECTOR);
|
|
}
|
|
|
|
$this->byCssSelector($colSelector)->click();
|
|
$this->elementWait("$this->colHeaderSelector #$columnID.sortDesc", self::CSS_SELECTOR);
|
|
}
|
|
}
|
|
|
|
|
|
/** Footer helper functions **/
|
|
public function navigateThroughPager($expectedPages, $pageCallback = null, int $fix = 7)
|
|
{
|
|
$this->waitUntilDomReadyState();
|
|
|
|
$this->assertCurrentPage(1);
|
|
$this->assertPreviousButtonExists(false);
|
|
|
|
// If only 1 page, then run the callback and go back.
|
|
if ($expectedPages === 1) {
|
|
$this->assertNextButtonExists(false);
|
|
|
|
if ($pageCallback) {
|
|
call_user_func($pageCallback, 1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
$this->assertNextButtonExists($this->shouldShowNextButton(1, $expectedPages));
|
|
|
|
// Navigate through each page - test the next button.
|
|
for ($page = 2; $page <= $expectedPages; $page++) {
|
|
|
|
if (!$this->shouldShowNextButton($page, $expectedPages)) {
|
|
$this->clickPageNumber($page);
|
|
} else {
|
|
$this->clickNext();
|
|
}
|
|
|
|
$this->waitUntilDomReadyState();
|
|
$this->assertCurrentPage($page);
|
|
|
|
$this->assertPreviousButtonExists($this->shouldShowPreviousButton($page, $expectedPages, $fix));
|
|
$this->assertNextButtonExists($this->shouldShowNextButton($page, $expectedPages, $fix));
|
|
|
|
if ($pageCallback) {
|
|
call_user_func($pageCallback, $page);
|
|
}
|
|
}
|
|
|
|
// Go back to page 1 - test the previous button
|
|
for ($page = $expectedPages - 1; $page >= 1; $page--) {
|
|
|
|
if ($this->shouldShowPreviousButton($page, $expectedPages,$fix)) {
|
|
$this->clickPrevious();
|
|
} else {
|
|
$this->clickPageNumber($page);
|
|
}
|
|
|
|
$this->waitUntilDomReadyState();
|
|
$this->assertCurrentPage($page);
|
|
|
|
$this->assertPreviousButtonExists($this->shouldShowPreviousButton($page, $expectedPages,$fix));
|
|
$this->assertNextButtonExists($this->shouldShowNextButton($page, $expectedPages,$fix));
|
|
}
|
|
}
|
|
|
|
public function shouldShowPreviousButton($currentPage, $totalPages, int $fix = 7): bool
|
|
{
|
|
/** Previous is displayed only if total pages over 7, and we are over the fifth page. */
|
|
return $totalPages > $fix && $currentPage > 4;
|
|
}
|
|
|
|
public function shouldShowNextButton($currentPage, $totalPages, int $fix = 7): bool
|
|
{
|
|
/** The Next button is displayed if there are more than 7 pages, and we're not on the last 4 pages. */
|
|
return $totalPages > $fix && $currentPage < $totalPages - 3;
|
|
}
|
|
|
|
public function clickNext(): void
|
|
{
|
|
$this->scrollIntoViewHoverClick(
|
|
"$this->tablePagerContainer/li[contains(@class, 'last')]//a[@class='nextp']",
|
|
self::XPATH,
|
|
);
|
|
}
|
|
|
|
public function clickPrevious(): void
|
|
{
|
|
$this->scrollIntoViewHoverClick(
|
|
"$this->tablePagerContainer/li[contains(@class, 'first')]//a[@class='prevp']",
|
|
self::XPATH,
|
|
);
|
|
}
|
|
|
|
public function clickPageNumber($pageNumber): void
|
|
{
|
|
$this->scrollIntoViewHoverClick("$this->tablePagerContainer/li[@id='$pageNumber']//a", self::XPATH);
|
|
}
|
|
|
|
public function assertPreviousButtonExists(bool $exists = true): void
|
|
{
|
|
$selector = "$this->tablePagerContainer/li[contains(@class, 'first')]//a[@class='prevp']";
|
|
$this->assertSame($exists, $this->elementExists($selector, self::XPATH, true));
|
|
}
|
|
|
|
public function assertNextButtonExists(bool $exists = true): void
|
|
{
|
|
$selector = "$this->tablePagerContainer/li[contains(@class, 'last')]//a[@class='nextp']";
|
|
$this->assertSame($exists, $this->elementExists($selector, self::XPATH, true));
|
|
}
|
|
|
|
public function assertCurrentPage($pageNumber): void
|
|
{
|
|
$selectedPage = $this->byXPath("$this->tablePagerContainer/li[contains(@class, 'sel')]");
|
|
$actualPage = (int)$selectedPage->getAttribute('id');
|
|
$this->assertEquals($pageNumber, $actualPage, "Wrong page. Expected: $pageNumber, actual: $actualPage");
|
|
|
|
// Check <a> has 'selected'
|
|
$link = $selectedPage->findElement(WebDriverBy::tagName('a'));
|
|
$msg = "Page $pageNumber link has not been marked as selected";
|
|
$this->assertStringContainsString('selected', $link->getAttribute('class'), $msg);
|
|
}
|
|
|
|
public function assertTablePagerCount(string $pager): void
|
|
{
|
|
$selector = "#cms-table-drop-marker .pager .rcount";
|
|
$this->assertStringContainsString($pager, $this->byCssSelector($selector)->getText());
|
|
}
|
|
|
|
public function toggleTableRowLastCheckbox(int $id, bool $tick = true): void
|
|
{
|
|
$chkSelector = "//ul[@id='$id']//li[not(contains(@class, 'first'))][last()]//div[@class='ncheckbox']";
|
|
$this->scrollElementIntoView($chkSelector, self::XPATH);
|
|
$element = $this->byXPath($chkSelector);
|
|
$isChecked = str_contains($element->getAttribute('class'), "chked");
|
|
if ($isChecked !== $tick) {
|
|
$element->click();
|
|
$this->waitUntilDomReadyState();
|
|
}
|
|
}
|
|
|
|
public function searchCmsTableByColumnId(int $id, bool $submit = true): void
|
|
{
|
|
$this->searchCmsTableByColumn('id', (string) $id, $submit);
|
|
}
|
|
|
|
public function sortCmsTableByColumnId(string $order = 'ASC'): void
|
|
{
|
|
$this->sortTableByColumn('h_id', $order);
|
|
}
|
|
|
|
public function validateTableOrderBy($page, array $expectedData): void
|
|
{
|
|
$col = 3;
|
|
foreach ($page->tableColumns as $tableColumn) {
|
|
if (in_array($tableColumn, $page->tableColumnsToSkip ?? [])) {
|
|
continue;
|
|
}
|
|
|
|
if (!isset($expectedData[$tableColumn])) {
|
|
$col++;
|
|
continue;
|
|
}
|
|
|
|
$this->sortTableByColumn($tableColumn);
|
|
$this->assertCmsTableRowData(1, $col, $expectedData[$tableColumn]['ASC']);
|
|
|
|
$this->sortTableByColumn($tableColumn, 'DESC');
|
|
$this->assertCmsTableRowData(1, $col, $expectedData[$tableColumn]['DESC']);
|
|
|
|
$col++;
|
|
}
|
|
}
|
|
}
|