diff --git a/src/bundle/Resources/config/services/test/pages.yaml b/src/bundle/Resources/config/services/test/pages.yaml index 9dbff56e96..2ca6d4bc2c 100644 --- a/src/bundle/Resources/config/services/test/pages.yaml +++ b/src/bundle/Resources/config/services/test/pages.yaml @@ -63,3 +63,6 @@ services: Ibexa\AdminUi\Behat\Page\UserSettingsPage: ~ Ibexa\AdminUi\Behat\Page\UserProfilePage: ~ + + Ibexa\AdminUi\Behat\Page\NotificationsViewAllPage: ~ + diff --git a/src/lib/Behat/BrowserContext/UserNotificationContext.php b/src/lib/Behat/BrowserContext/UserNotificationContext.php index 1d4bb7506e..a08f04f721 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\NotificationsViewAllPage; use PHPUnit\Framework\Assert; class UserNotificationContext implements Context @@ -22,10 +23,13 @@ class UserNotificationContext implements Context /** @var \Ibexa\AdminUi\Behat\Component\UserNotificationPopup */ private $userNotificationPopup; - public function __construct(UpperMenu $upperMenu, UserNotificationPopup $userNotificationPopup) + private NotificationsViewAllPage $notificationsViewAllPage; + + public function __construct(UpperMenu $upperMenu, UserNotificationPopup $userNotificationPopup, NotificationsViewAllPage $notificationsViewAllPage) { $this->upperMenu = $upperMenu; $this->userNotificationPopup = $userNotificationPopup; + $this->notificationsViewAllPage = $notificationsViewAllPage; } /** @@ -47,4 +51,133 @@ public function iGoToUserNotificationWithDetails(TableNode $notificationDetails) $this->userNotificationPopup->verifyIsLoaded(); $this->userNotificationPopup->clickNotification($type, $description); } + + /** + * @Then the notification appears with details: + */ + public function notificationAppearsWithDetails(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); + } + + /** + * @When I open notification menu with description :description + */ + public function iOpenNotificationMenu(string $description): void + { + $this->userNotificationPopup->openNotificationMenu($description); + } + + /** + * @When I perform the :action action on the notification + */ + public function iPerformNotificationAction(string $action): void + { + $this->userNotificationPopup->clickActionButton($action); + } + + /** + * @Then the notification should have :expectedAction action available + */ + public function verifyNotificationAction(string $expectedAction): void + { + $this->userNotificationPopup->findActionButton($expectedAction); + } + + /** + * @When I mark all notifications as read + */ + public function markAllNotificationsAsRead(): void + { + $this->userNotificationPopup->verifyIsLoaded(); + $this->userNotificationPopup->clickOnMarkAllAsReadButton(); + } + + /** + * @When I go to the list of all notifications + */ + public function goToAllNotificationsList(): void + { + $this->userNotificationPopup->verifyIsLoaded(); + $this->userNotificationPopup->clickViewAllNotificationsButton(); + } + + /** + * @Then there is :notificationTitle notification on list + */ + public function thereIsNotificationOnList(string $notificationTitle): void + { + $this->notificationsViewAllPage->verifyIsLoaded(); + $this->notificationsViewAllPage->verifyNotificationIsOnList($notificationTitle); + } + + /** + * @When I mark notification as unread with title :notificationTitle + */ + public function iMarkNotificationAsUnread(string $notificationTitle): void + { + $this->notificationsViewAllPage->markAsUnread($notificationTitle); + } + + /** + * @Then the notification with title :notificationTitle has status :notificationStatus + */ + public function verifyNotificationStatus(string $notificationTitle, string $notificationStatus): void + { + Assert::assertEquals($notificationStatus, $this->notificationsViewAllPage->getStatusForNotification($notificationTitle)); + } + + /** + * @When I go to content of notification with title :notificationTitle + */ + public function iGoToContent(string $notificationTitle): void + { + $this->notificationsViewAllPage->goToContent($notificationTitle); + } + + /** + * @When I delete notification with title :notificationTitle + */ + public function iDeleteNotification(string $notificationTitle): void + { + $this->notificationsViewAllPage->deleteNotification($notificationTitle); + } + + /** + * @Then an empty notifications state in the popup should be visible + */ + public function emptyNotificationsStateInPopup(): void + { + $this->userNotificationPopup->assertEmptyStateVisible(); + } + + /** + * @Then an empty notifications state should be visible in the All Notifications view + */ + public function emptyNotificationsStateInAllNotificationsView(): void + { + $this->notificationsViewAllPage->assertEmptyStateVisible(); + } + + /** + * @Then the notifications popup counter should display :expectedCount + */ + public function iShouldSeeNotificationsCountInPopup(int $expectedCount): void + { + $this->userNotificationPopup->verifyNotificationsCount($expectedCount); + } + + /** + * @Then I should see :expectedCount notifications in the All Notifications view + */ + public function thereShouldBeNotificationsInAllNotificationsView(int $expectedCount): void + { + $this->notificationsViewAllPage->verifyNotificationsCount($expectedCount); + } } diff --git a/src/lib/Behat/Component/UserNotificationPopup.php b/src/lib/Behat/Component/UserNotificationPopup.php index 4b8c780c89..209537de6e 100644 --- a/src/lib/Behat/Component/UserNotificationPopup.php +++ b/src/lib/Behat/Component/UserNotificationPopup.php @@ -10,6 +10,12 @@ use Exception; use Ibexa\Behat\Browser\Component\Component; +use Ibexa\Behat\Browser\Element\Action\MouseOverAndClick; +use Ibexa\Behat\Browser\Element\Condition\ElementExistsCondition; +use Ibexa\Behat\Browser\Element\Condition\ElementNotExistsCondition; +use Ibexa\Behat\Browser\Element\Criterion\ChildElementTextCriterion; +use Ibexa\Behat\Browser\Element\Criterion\ElementTextCriterion; +use Ibexa\Behat\Browser\Element\ElementInterface; use Ibexa\Behat\Browser\Locator\VisibleCSSLocator; class UserNotificationPopup extends Component @@ -41,6 +47,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; + } + } + + if ($shouldExist) { + return; + } 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 openNotificationMenu(string $expectedDescription): void + { + $this->getHTMLPage()->setTimeout(5)->findAll($this->getLocator('notificationItem')) + ->filterBy(new ChildElementTextCriterion($this->getLocator('notificationDescriptionText'), $expectedDescription)) + ->first()->find($this->getLocator('notificationMenuButton'))->click(); + + $this->getHTMLPage() + ->setTimeout(10) + ->waitUntilCondition( + new ElementExistsCondition( + $this->getHTMLPage(), + $this->getLocator('notificationActionsPopup'), + ) + ); + } + + public function clickActionButton(string $buttonText): void + { + $this->getHTMLPage() + ->setTimeout(10) + ->findAll($this->getLocator('notificationMenuItemContent')) + ->filterBy(new ElementTextCriterion($buttonText))->first()->execute(new MouseOverAndClick()); + + $this->getHTMLPage() + ->setTimeout(10) + ->waitUntilCondition( + new ElementNotExistsCondition( + $this->getHTMLPage(), + $this->getLocator('notificationActionsPopup') + ) + ); + } + + public function findActionButton(string $buttonText): ?ElementInterface + { + $this->getHTMLPage() + ->setTimeout(10) + ->waitUntilCondition( + new ElementExistsCondition( + $this->getHTMLPage(), + $this->getLocator('notificationMenuItemContent') + ) + ); + + return $this->getHTMLPage() + ->setTimeout(10) + ->findAll($this->getLocator('notificationMenuItemContent')) + ->filterBy(new ElementTextCriterion($buttonText)) + ->first(); + } + + public function assertEmptyStateVisible(): void + { + $this->getHTMLPage()->setTimeout(5)->find($this->getLocator('notificationsEmptyText'))->assert()->isVisible(); + } + + 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 verifyNotificationsCount(int $expectedCount): void + { + $this->getHTMLPage()->setTimeout(10)->find($this->getLocator('notificationsCount'))->assert()->textEquals('(' . $expectedCount . ')'); + } + public function verifyIsLoaded(): void { $this->getHTMLPage() @@ -57,6 +177,14 @@ 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('markAllAsReadButton', '.ibexa-notifications-modal__mark-all-read-btn'), + new VisibleCSSLocator('viewAllNotificationsButton', '.ibexa-notifications-modal__view-all-btn'), + new VisibleCSSLocator('notificationActionsPopup', '.ibexa-notification-actions-popup-menu:not(.ibexa-popup-menu--hidden)'), + new VisibleCSSLocator('notificationsEmptyText', '.ibexa-notifications-modal__empty-text'), + new VisibleCSSLocator('notificationsCount', '.ibexa-notifications-modal__count'), ]; } } diff --git a/src/lib/Behat/Page/NotificationsViewAllPage.php b/src/lib/Behat/Page/NotificationsViewAllPage.php new file mode 100644 index 0000000000..7ae2aeb624 --- /dev/null +++ b/src/lib/Behat/Page/NotificationsViewAllPage.php @@ -0,0 +1,122 @@ +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 markAsUnread(string $notificationTitle): void + { + $this->getHTMLPage()->setTimeout(10); + $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 assertEmptyStateVisible(): void + { + $this->getHTMLPage()->setTimeout(5)->find($this->getLocator('notificationsEmptyText'))->assert()->isVisible(); + } + + public function verifyNotificationsCount(int $expectedCount): void + { + $actualCount = $this->getHTMLPage()->setTimeout(5)->find($this->getLocator('notificationsTotalCount'))->getAttribute('data-notifications-total'); + + if ($actualCount !== (string)$expectedCount) { + throw new \Exception(sprintf( + 'Expected notification count %d, but got %s.', + $expectedCount, + $actualCount + )); + } + } + + 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'), + new VisibleCSSLocator('notificationsEmptyText', '.ibexa-table__empty-table-info-text'), + new VisibleCSSLocator('notificationsTotalCount', 'table[data-notifications-total]'), + ]; + } +}