diff --git a/src/bundle/Resources/config/services/test/pages.yaml b/src/bundle/Resources/config/services/test/pages.yaml index 9dbff56e96..d8f80f80e2 100644 --- a/src/bundle/Resources/config/services/test/pages.yaml +++ b/src/bundle/Resources/config/services/test/pages.yaml @@ -63,3 +63,5 @@ services: Ibexa\AdminUi\Behat\Page\UserSettingsPage: ~ Ibexa\AdminUi\Behat\Page\UserProfilePage: ~ + + Ibexa\AdminUi\Behat\Page\NotificationsPage: ~ diff --git a/src/lib/Behat/BrowserContext/UserNotificationContext.php b/src/lib/Behat/BrowserContext/UserNotificationContext.php index 1d4bb7506e..eac3b16dd6 100644 --- a/src/lib/Behat/BrowserContext/UserNotificationContext.php +++ b/src/lib/Behat/BrowserContext/UserNotificationContext.php @@ -12,6 +12,7 @@ use Behat\Gherkin\Node\TableNode; use Ibexa\AdminUi\Behat\Component\UpperMenu; use Ibexa\AdminUi\Behat\Component\UserNotificationPopup; +use Ibexa\AdminUi\Behat\Page\NotificationsPage; use PHPUnit\Framework\Assert; class UserNotificationContext implements Context @@ -22,10 +23,15 @@ class UserNotificationContext implements Context /** @var \Ibexa\AdminUi\Behat\Component\UserNotificationPopup */ private $userNotificationPopup; - public function __construct(UpperMenu $upperMenu, UserNotificationPopup $userNotificationPopup) + private NotificationsPage $notificationsPage; + + private int $previousCount; + + public function __construct(UpperMenu $upperMenu, UserNotificationPopup $userNotificationPopup, NotificationsPage $notificationsPage) { $this->upperMenu = $upperMenu; $this->userNotificationPopup = $userNotificationPopup; + $this->notificationsPage = $notificationsPage; } /** @@ -47,4 +53,133 @@ public function iGoToUserNotificationWithDetails(TableNode $notificationDetails) $this->userNotificationPopup->verifyIsLoaded(); $this->userNotificationPopup->clickNotification($type, $description); } + + /** + * @Then the notification appears with details: + */ + public function thereIsNotificationAppearsWithDetails(TableNode $notificationDetails): void + { + $type = $notificationDetails->getHash()[0]['Type']; + $author = $notificationDetails->getHash()[0]['Author']; + $description = $notificationDetails->getHash()[0]['Description']; + $date = $notificationDetails->getHash()[0]['Date']; + + $this->userNotificationPopup->verifyIsLoaded(); + $this->userNotificationPopup->verifyNotification($type, $author, $description, $date, true); + } + + /** + * @Then no notification appears with details: + */ + public function noNotificationAppearsWithDetails(TableNode $notificationDetails): void + { + $type = $notificationDetails->getHash()[0]['Type']; + $author = $notificationDetails->getHash()[0]['Author']; + $description = $notificationDetails->getHash()[0]['Description']; + $date = $notificationDetails->getHash()[0]['Date']; + + $this->userNotificationPopup->verifyIsLoaded(); + $this->userNotificationPopup->verifyNotification($type, $author, $description, $date, false); + } + + /** + * @When I open notification menu with description :description + */ + public function iOpenNotificationMenu(string $description): void + { + $this->userNotificationPopup->openNotificationMenu($description); + } + + /** + * @When I click :action action + */ + public function iClickActionButton(string $action): void + { + $this->userNotificationPopup->clickActionButton($action); + } + + /** + * @When I store current notification count + */ + public function storeNotificationCount(): void + { + $this->userNotificationPopup->verifyIsLoaded(); + $this->previousCount = $this->userNotificationPopup->getNotificationCount(); + } + + /** + * @Then the notification count should change in :direction direction + */ + public function verifyNotificationCountChanged(string $direction): void + { + $this->userNotificationPopup->verifyNotificationCountChanged($this->previousCount, $direction); + } + + /** + * @When I click mark all as read button + */ + public function iClickButton(): void + { + $this->userNotificationPopup->verifyIsLoaded(); + $this->userNotificationPopup->clickOnMarkAllAsReadButton(); + } + + /** + * @When I click view all notifications button + */ + public function iClickViewAllNotificationsButton(): void + { + $this->userNotificationPopup->verifyIsLoaded(); + $this->userNotificationPopup->clickViewAllNotificationsButton(); + } + + /** + * @Then there is :notificationTitle notification on list + */ + public function thereIsNotificationOnList(string $notificationTitle): void + { + $this->notificationsPage->verifyIsLoaded(); + $this->notificationsPage->verifyNotificationIsOnList($notificationTitle); + } + + /** + * @Then there is no :notificationTitle notification on list + */ + public function thereIsNoNotificationOnList(string $notificationTitle): void + { + $this->notificationsPage->verifyIsLoaded(); + $this->notificationsPage->verifyNotificationIsNotOnList($notificationTitle); + } + + /** + * @When I mark notification as unread with title :notificationTitle + */ + public function iMarkNotificationAsUnread(string $notificationTitle): void + { + $this->notificationsPage->markAsUnread($notificationTitle); + } + + /** + * @Then the notification with title :notificationTitle has status :notificationStatus + */ + public function verifyNotificationStatus(string $notificationTitle, string $notificationStatus): void + { + Assert::assertEquals($notificationStatus, $this->notificationsPage->getStatusForNotification($notificationTitle)); + } + + /** + * @When I go to content of notification with title :notificationTitle + */ + public function iGoToContent(string $notificationTitle): void + { + $this->notificationsPage->goToContent($notificationTitle); + } + + /** + * @When I delete notification with title :notificationTitle + */ + public function iDeleteNotification(string $notificationTitle): void + { + $this->notificationsPage->deleteNotification($notificationTitle); + } } diff --git a/src/lib/Behat/Component/UserNotificationPopup.php b/src/lib/Behat/Component/UserNotificationPopup.php index 4b8c780c89..66586f1423 100644 --- a/src/lib/Behat/Component/UserNotificationPopup.php +++ b/src/lib/Behat/Component/UserNotificationPopup.php @@ -10,6 +10,9 @@ use Exception; use Ibexa\Behat\Browser\Component\Component; +use Ibexa\Behat\Browser\Element\Action\MouseOverAndClick; +use Ibexa\Behat\Browser\Element\Criterion\ChildElementTextCriterion; +use Ibexa\Behat\Browser\Element\Criterion\ElementTextCriterion; use Ibexa\Behat\Browser\Locator\VisibleCSSLocator; class UserNotificationPopup extends Component @@ -41,6 +44,120 @@ public function clickNotification(string $expectedType, string $expectedDescript throw new Exception(sprintf('Notification of type: %s with description: %d not found', $expectedType, $expectedDescription)); } + public function verifyNotification(string $expectedType, string $expectedAuthor, string $expectedDescription, ?string $expectedDate = null, bool $shouldExist = true): void + { + $notifications = $this->getHTMLPage()->setTimeout(5)->findAll($this->getLocator('notificationItem')); + + foreach ($notifications as $notification) { + $criteria = [ + new ChildElementTextCriterion($this->getLocator('notificationType'), $expectedType), + new ChildElementTextCriterion($this->getLocator('notificationDescriptionTitle'), $expectedAuthor), + new ChildElementTextCriterion($this->getLocator('notificationDescriptionText'), $expectedDescription), + ]; + + if ($expectedDate !== null && $expectedDate !== 'XXXX-XX-XX') { + $criteria[] = new ChildElementTextCriterion($this->getLocator('notificationDate'), $expectedDate); + } + + foreach ($criteria as $criterion) { + if (!$criterion->matches($notification)) { + continue 2; // go to next notification + } + } + + if ($shouldExist) { + return; // matching notification found + } else { + throw new \Exception(sprintf( + 'Notification of type "%s" with author "%s" and description "%s" should not exist, but was found.', + $expectedType, + $expectedAuthor, + $expectedDescription + )); + } + } + + if ($shouldExist) { + throw new \Exception(sprintf( + 'Notification of type "%s" with author "%s" and description "%s" was not found.', + $expectedType, + $expectedAuthor, + $expectedDescription + )); + } + } + + public function getNotificationCount(): int + { + try { + $counterElement = $this->getHTMLPage()->find($this->getLocator('notificationCounterDot')); + $countText = $counterElement->getAttribute('data-count'); + + return (int) $countText; + } catch (Exception $e) { + return 0; + } + } + + public function verifyNotificationCountChanged(int $previousCount, string $direction): void + { + $attempts = 10; + $interval = 500000; + $currentCount = 0; + + for ($i = 0; $i < $attempts; ++$i) { + $currentCount = $this->getNotificationCount(); + + if (($direction === 'increase' && $currentCount > $previousCount) || + ($direction === 'decrease' && $currentCount < $previousCount)) { + return; + } + + usleep($interval); + } + + throw new \Exception(sprintf( + 'Expected notification count to %s (previous: %d, current: %d)', + $direction, + $previousCount, + $currentCount + )); + } + + public function openNotificationMenu(string $expectedDescription): void + { + $notifications = $this->getHTMLPage() + ->setTimeout(5) + ->findAll($this->getLocator('notificationItem')) + ->filterBy(new ChildElementTextCriterion( + $this->getLocator('notificationDescriptionText'), + $expectedDescription + )); + + $menuButton = $notifications->first()->find($this->getLocator('notificationMenuButton')); + $menuButton->click(); + } + + public function clickActionButton(string $buttonText): void + { + $buttons = $this->getHTMLPage() + ->setTimeout(5) + ->findAll($this->getLocator('notificationMenuItemContent')) + ->filterBy(new ElementTextCriterion($buttonText)); + + $buttons->first()->execute(new MouseOverAndClick()); + } + + public function clickOnMarkAllAsReadButton(): void + { + $this->getHTMLPage()->setTimeout(5)->find($this->getLocator('markAllAsReadButton'))->click(); + } + + public function clickViewAllNotificationsButton(): void + { + $this->getHTMLPage()->setTimeout(3)->find($this->getLocator('viewAllNotificationsButton'))->click(); + } + public function verifyIsLoaded(): void { $this->getHTMLPage() @@ -57,6 +174,12 @@ protected function specifyLocators(): array new VisibleCSSLocator('notificationType', '.ibexa-notifications-modal__type-content .type__text'), new VisibleCSSLocator('notificationDescriptionTitle', '.ibexa-notifications-modal__description .description__title'), new VisibleCSSLocator('notificationDescriptionText', '.ibexa-notifications-modal__type-content .description__text'), + new VisibleCSSLocator('notificationDate', '.ibexa-notifications-modal__item--date'), + new VisibleCSSLocator('notificationMenuButton', '.ibexa-notifications-modal__actions'), + new VisibleCSSLocator('notificationMenuItemContent', '.ibexa-popup-menu__item-content.ibexa-multilevel-popup-menu__item-content'), + new VisibleCSSLocator('notificationCounterDot', '.ibexa-header-user-menu__notice-dot'), + new VisibleCSSLocator('markAllAsReadButton', '.ibexa-notifications-modal__mark-all-read-btn'), + new VisibleCSSLocator('viewAllNotificationsButton', '.ibexa-notifications-modal__view-all-btn'), ]; } } diff --git a/src/lib/Behat/Page/NotificationsPage.php b/src/lib/Behat/Page/NotificationsPage.php new file mode 100644 index 0000000000..d215392303 --- /dev/null +++ b/src/lib/Behat/Page/NotificationsPage.php @@ -0,0 +1,108 @@ +table = $tableBuilder->newTable()->build(); + $this->dialog = $dialog; + } + + public function verifyNotificationIsOnList(string $notificationTitle): void + { + if (!$this->table->hasElement(['Title' => $notificationTitle])) { + throw new \Exception(sprintf('Notification "%s" not found on list.', $notificationTitle)); + } + } + + public function verifyNotificationIsNotOnList(string $notificationTitle): void + { + if ($this->table->hasElement(['Title' => $notificationTitle])) { + throw new \Exception(sprintf('Notification "%s" is still present on list.', $notificationTitle)); + } + } + + public function markAsUnread(string $notificationTitle): void + { + $this->getHTMLPage()->setTimeout(5); + $this->table->getTableRow(['Title' => $notificationTitle])->click($this->getLocator('markAsUnreadButton')); + } + + public function goToContent(string $notificationTitle): void + { + $this->getHTMLPage()->setTimeout(5); + $this->table->getTableRow(['Title' => $notificationTitle])->click($this->getLocator('goToContentButton')); + } + + public function deleteNotification(string $notificationTitle): void + { + $this->table->getTableRow(['Title' => $notificationTitle])->click($this->getLocator('notificationCheckbox')); + + $this->getHTMLPage()->setTimeout(3)->find($this->getLocator('deleteButton'))->click(); + $this->dialog->verifyIsLoaded(); + $this->dialog->confirm(); + } + + public function getStatusForNotification(string $notificationTitle): string + { + return $this->getHTMLPage()->findAll($this->getLocator('tableRow')) + ->getByCriterion(new ChildElementTextCriterion($this->getLocator('rowName'), $notificationTitle)) + ->find($this->getLocator('rowStatus'))->getText(); + } + + public function verifyIsLoaded(): void + { + $this->getHTMLPage()->setTimeout(5)->find($this->getLocator('pageTitle'))->assert()->textContains('Notifications'); + } + + public function getName(): string + { + return 'Notifications'; + } + + protected function getRoute(): string + { + return '/notifications/render/all'; + } + + protected function specifyLocators(): array + { + return [ + new VisibleCSSLocator('pageTitle', '.ibexa-page-title h1'), + new VisibleCSSLocator('tableRow', 'tr'), + new VisibleCSSLocator('rowName', '.ibexa-notification-view-all__details'), + new VisibleCSSLocator('rowStatus', '.ibexa-notification-view-all__status'), + new VisibleCSSLocator('markAsUnreadButton', '[data-original-title="Mark as unread"]'), + new VisibleCSSLocator('goToContentButton', '[data-original-title="Go to content"]'), + new VisibleCSSLocator('deleteButton', '.ibexa-notification-list__btn-delete'), + new VisibleCSSLocator('notificationCheckbox', '.ibexa-notification-list__mark-row-checkbox'), + ]; + } +}