From 6eb84146e92aef85c8c6edf2c57f5d2fc32ae817 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 14 Jun 2022 18:12:38 -0300 Subject: [PATCH 01/11] [BREAK] Deactivated team members are added to auto-join rooms (#25016) ## Proposed changes (including videos or screenshots) - Do not add deactivated users to auto-join rooms. ## Issue(s) ## Steps to test or reproduce 1. Create a new team and add a team member to it; 2. Go to **Administration > Users** and deactivate one of the team members; 3. Return to the team and add an auto-join channel/group. **_Expected behavior:_** the deactivated user should not be added to the room. ## Further comments --- apps/meteor/app/models/server/raw/Users.js | 6 ++++++ apps/meteor/server/services/team/service.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/models/server/raw/Users.js b/apps/meteor/app/models/server/raw/Users.js index da01f11cbd3c..88c36e118aee 100644 --- a/apps/meteor/app/models/server/raw/Users.js +++ b/apps/meteor/app/models/server/raw/Users.js @@ -147,6 +147,12 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } + findActive(query, options = {}) { + Object.assign(query, { active: true }); + + return this.find(query, options); + } + findActiveByIds(userIds, options = {}) { const query = { _id: { $in: userIds }, diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts index 7df0e05958e7..bcc44b0a9be7 100644 --- a/apps/meteor/server/services/team/service.ts +++ b/apps/meteor/server/services/team/service.ts @@ -647,7 +647,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService { }; } - const users = await this.Users.find({ ...query }).toArray(); + const users = await this.Users.findActive({ ...query }).toArray(); const userIds = users.map((m) => m._id); const cursor = this.TeamMembersModel.findMembersInfoByTeamId(teamId, count, offset, { userId: { $in: userIds }, From bff2f4af8a39a1c134e23a4906d8af1ea19f72f5 Mon Sep 17 00:00:00 2001 From: souzaramon Date: Wed, 15 Jun 2022 09:36:27 -0300 Subject: [PATCH 02/11] Chore: update pageobjects to use es6 getters and remove export default (#25867) ## Proposed changes (including videos or screenshots) - Simplify imports of pageobjects - Refactor pageobjects to use es6 getters - Move BagePage stuff to Global page object --- apps/meteor/tests/e2e/00-wizard.spec.ts | 24 +- .../tests/e2e/01-forgot-password.spec.ts | 17 +- apps/meteor/tests/e2e/02-register.spec.ts | 2 +- apps/meteor/tests/e2e/03-login.spec.ts | 5 +- .../tests/e2e/04-main-elements-render.spec.ts | 83 ++-- .../tests/e2e/05-channel-creation.spec.ts | 3 +- apps/meteor/tests/e2e/06-messaging.spec.ts | 85 ++-- apps/meteor/tests/e2e/07-emoji.spec.ts | 60 ++- apps/meteor/tests/e2e/08-resolutions.spec.ts | 45 +- apps/meteor/tests/e2e/09-channel.spec.ts | 88 ++-- .../tests/e2e/10-user-preferences.spec.ts | 44 +- apps/meteor/tests/e2e/11-admin.spec.ts | 345 +++++++-------- apps/meteor/tests/e2e/12-settings.spec.ts | 44 +- apps/meteor/tests/e2e/13-permissions.spec.ts | 34 +- .../tests/e2e/14-setting-permissions.spec.ts | 82 ++-- .../meteor/tests/e2e/15-message-popup.spec.ts | 14 +- apps/meteor/tests/e2e/16-discussion.spec.ts | 9 +- .../tests/e2e/omnichannel-agents.spec.ts | 67 +-- .../e2e/omnichannel-departaments.spec.ts | 9 +- .../tests/e2e/pageobjects/Administration.ts | 365 ++++++++++++++++ apps/meteor/tests/e2e/pageobjects/Agents.ts | 110 +++++ apps/meteor/tests/e2e/pageobjects/BasePage.ts | 21 + .../tests/e2e/pageobjects/ChannelCreation.ts | 75 ++++ .../{utils => }/pageobjects/Departments.ts | 44 +- .../tests/e2e/pageobjects/Discussion.ts | 54 +++ apps/meteor/tests/e2e/pageobjects/FlexTab.ts | 410 ++++++++++++++++++ apps/meteor/tests/e2e/pageobjects/Global.ts | 54 +++ .../meteor/tests/e2e/pageobjects/LoginPage.ts | 119 +++++ .../tests/e2e/pageobjects/MainContent.ts | 345 +++++++++++++++ .../e2e/pageobjects/PreferencesMainContent.ts | 46 ++ .../tests/e2e/pageobjects/SetupWizard.ts | 172 ++++++++ apps/meteor/tests/e2e/pageobjects/SideNav.ts | 180 ++++++++ apps/meteor/tests/e2e/pageobjects/index.ts | 12 + .../e2e/utils/pageobjects/Administration.ts | 365 ---------------- .../tests/e2e/utils/pageobjects/Agents.ts | 110 ----- .../tests/e2e/utils/pageobjects/BasePage.ts | 42 -- .../e2e/utils/pageobjects/ChannelCreation.ts | 75 ---- .../tests/e2e/utils/pageobjects/Discussion.ts | 54 --- .../tests/e2e/utils/pageobjects/FlexTab.ts | 407 ----------------- .../tests/e2e/utils/pageobjects/Global.ts | 38 -- .../tests/e2e/utils/pageobjects/LoginPage.ts | 119 ----- .../e2e/utils/pageobjects/MainContent.ts | 345 --------------- .../pageobjects/PreferencesMainContent.ts | 46 -- .../e2e/utils/pageobjects/SetupWizard.ts | 172 -------- .../tests/e2e/utils/pageobjects/SideNav.ts | 180 -------- .../tests/e2e/utils/pageobjects/index.ts | 9 - .../tests/e2e/utils/types/ChatContext.ts | 7 - 47 files changed, 2486 insertions(+), 2550 deletions(-) create mode 100644 apps/meteor/tests/e2e/pageobjects/Administration.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/Agents.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/BasePage.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/ChannelCreation.ts rename apps/meteor/tests/e2e/{utils => }/pageobjects/Departments.ts (60%) create mode 100644 apps/meteor/tests/e2e/pageobjects/Discussion.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/FlexTab.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/Global.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/LoginPage.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/MainContent.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/PreferencesMainContent.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/SetupWizard.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/SideNav.ts create mode 100644 apps/meteor/tests/e2e/pageobjects/index.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/Administration.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/Agents.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/BasePage.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/ChannelCreation.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/Discussion.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/FlexTab.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/Global.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/LoginPage.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/MainContent.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/PreferencesMainContent.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/SetupWizard.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/SideNav.ts delete mode 100644 apps/meteor/tests/e2e/utils/pageobjects/index.ts delete mode 100644 apps/meteor/tests/e2e/utils/types/ChatContext.ts diff --git a/apps/meteor/tests/e2e/00-wizard.spec.ts b/apps/meteor/tests/e2e/00-wizard.spec.ts index 2a1b9f1c2920..ee404312d180 100644 --- a/apps/meteor/tests/e2e/00-wizard.spec.ts +++ b/apps/meteor/tests/e2e/00-wizard.spec.ts @@ -1,14 +1,14 @@ import { test, expect } from '@playwright/test'; -import SetupWizard from './utils/pageobjects/SetupWizard'; import { VALID_EMAIL, adminLogin } from './utils/mocks/userAndPasswordMock'; import { setupWizardStepRegex } from './utils/mocks/urlMock'; import { HOME_SELECTOR } from './utils/mocks/waitSelectorsMock'; -import LoginPage from './utils/pageobjects/LoginPage'; +import { LoginPage, SetupWizard } from './pageobjects'; test.describe('[Wizard]', () => { let setupWizard: SetupWizard; let loginPage: LoginPage; + test.beforeEach(async ({ page }) => { setupWizard = new SetupWizard(page); loginPage = new LoginPage(page); @@ -27,7 +27,7 @@ test.describe('[Wizard]', () => { test('expect go to Step 3 successfully', async () => { await setupWizard.stepTwoSuccess(); - await expect(setupWizard.getPage()).toHaveURL(setupWizardStepRegex._3); + await expect(setupWizard.page).toHaveURL(setupWizardStepRegex._3); }); }); @@ -39,11 +39,11 @@ test.describe('[Wizard]', () => { }); test('expect have email field to register the server', async () => { - await expect(setupWizard.registeredServer()).toBeVisible(); + await expect(setupWizard.registeredServer).toBeVisible(); }); test('expect start "Register" button disabled', async () => { - await expect(setupWizard.registerButton()).toBeDisabled(); + await expect(setupWizard.registerButton).toBeDisabled(); }); test('expect show an error on invalid email', async () => { @@ -51,13 +51,13 @@ test.describe('[Wizard]', () => { }); test('expect enable "Register" button when email is valid and terms checked', async () => { - await setupWizard.registeredServer().type(VALID_EMAIL); - await setupWizard.agreementField().click(); - await expect(setupWizard.registerButton()).toBeEnabled(); + await setupWizard.registeredServer.type(VALID_EMAIL); + await setupWizard.agreementField.click(); + await expect(setupWizard.registerButton).toBeEnabled(); }); test('expect have option for standalone server', async () => { - await expect(setupWizard.standaloneServer()).toBeVisible(); + await expect(setupWizard.standaloneServer).toBeVisible(); }); }); @@ -70,12 +70,12 @@ test.describe('[Wizard]', () => { }); test('expect confirm the standalone option', async () => { - await expect(setupWizard.goToWorkspace()).toBeVisible(); - await expect(setupWizard.standaloneConfirmText()).toBeVisible(); + await expect(setupWizard.goToWorkspace).toBeVisible(); + await expect(setupWizard.standaloneConfirmText).toBeVisible(); }); test('expect confirm standalone', async () => { - await setupWizard.goToWorkspace().click(); + await setupWizard.goToWorkspace.click(); await setupWizard.waitForSelector(HOME_SELECTOR); }); }); diff --git a/apps/meteor/tests/e2e/01-forgot-password.spec.ts b/apps/meteor/tests/e2e/01-forgot-password.spec.ts index 7f087fff746c..3f2c1edeb03e 100644 --- a/apps/meteor/tests/e2e/01-forgot-password.spec.ts +++ b/apps/meteor/tests/e2e/01-forgot-password.spec.ts @@ -1,7 +1,6 @@ import { test, expect } from '@playwright/test'; -import LoginPage from './utils/pageobjects/LoginPage'; -import Global from './utils/pageobjects/Global'; +import { Global, LoginPage } from './pageobjects'; import { VALID_EMAIL, INVALID_EMAIL, INVALID_EMAIL_WITHOUT_MAIL_PROVIDER } from './utils/mocks/userAndPasswordMock'; test.describe('[Forgot Password]', () => { @@ -19,24 +18,24 @@ test.describe('[Forgot Password]', () => { test('expect be required', async () => { loginPage.submit(); - await expect(loginPage.emailInvalidText()).toBeVisible(); + await expect(loginPage.emailInvalidText).toBeVisible(); }); test('expect invalid for email without domain', async () => { - await loginPage.emailField().type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); + await loginPage.emailField.type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); await loginPage.submit(); - await expect(loginPage.emailInvalidText()).toBeVisible(); + await expect(loginPage.emailInvalidText).toBeVisible(); }); test('expect be invalid for email with invalid domain', async () => { - await loginPage.emailField().type(INVALID_EMAIL); + await loginPage.emailField.type(INVALID_EMAIL); await loginPage.submit(); - await expect(loginPage.emailInvalidText()).toBeVisible(); + await expect(loginPage.emailInvalidText).toBeVisible(); }); test('expect user type a valid email', async () => { - await loginPage.emailField().type(VALID_EMAIL); + await loginPage.emailField.type(VALID_EMAIL); await loginPage.submit(); - await expect(global.getToastBarSuccess()).toBeVisible(); + await expect(global.getToastBarSuccess).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/02-register.spec.ts b/apps/meteor/tests/e2e/02-register.spec.ts index 0e2503956462..ec81337f05e7 100644 --- a/apps/meteor/tests/e2e/02-register.spec.ts +++ b/apps/meteor/tests/e2e/02-register.spec.ts @@ -1,7 +1,7 @@ import { test } from '@playwright/test'; import { registerUser, WRONG_PASSWORD } from './utils/mocks/userAndPasswordMock'; -import LoginPage from './utils/pageobjects/LoginPage'; +import { LoginPage } from './pageobjects'; test.describe('[Register]', () => { let loginPage: LoginPage; diff --git a/apps/meteor/tests/e2e/03-login.spec.ts b/apps/meteor/tests/e2e/03-login.spec.ts index 223262a0afb8..17b8a558e8ba 100644 --- a/apps/meteor/tests/e2e/03-login.spec.ts +++ b/apps/meteor/tests/e2e/03-login.spec.ts @@ -1,8 +1,7 @@ import { test, expect } from '@playwright/test'; import { validUser } from './utils/mocks/userAndPasswordMock'; -import LoginPage from './utils/pageobjects/LoginPage'; -import Global from './utils/pageobjects/Global'; +import { Global, LoginPage } from './pageobjects'; import { HOME_SELECTOR } from './utils/mocks/waitSelectorsMock'; test.describe('[Login]', () => { @@ -22,7 +21,7 @@ test.describe('[Login]', () => { password: 'any_password1', }; await loginPage.login(invalidUserPassword); - await expect(global.getToastBarError()).toBeVisible(); + await expect(global.getToastBarError).toBeVisible(); }); test('expect user make login', async () => { diff --git a/apps/meteor/tests/e2e/04-main-elements-render.spec.ts b/apps/meteor/tests/e2e/04-main-elements-render.spec.ts index 497e08c35c9a..7f3106d813b5 100644 --- a/apps/meteor/tests/e2e/04-main-elements-render.spec.ts +++ b/apps/meteor/tests/e2e/04-main-elements-render.spec.ts @@ -1,9 +1,6 @@ import { test, expect } from '@playwright/test'; -import MainContent from './utils/pageobjects/MainContent'; -import SideNav from './utils/pageobjects/SideNav'; -import FlexTab from './utils/pageobjects/FlexTab'; -import LoginPage from './utils/pageobjects/LoginPage'; +import { LoginPage, FlexTab, SideNav, MainContent } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; test.describe('[Main Elements Render]', function () { @@ -28,64 +25,64 @@ test.describe('[Main Elements Render]', function () { test.describe('[Side Nav Bar]', () => { test.describe('[Render]', () => { test('expect show the new channel button', async () => { - await expect(sideNav.newChannelBtnToolbar()).toBeVisible(); + await expect(sideNav.newChannelBtnToolbar).toBeVisible(); }); test('expect show "general" channel', async () => { - await expect(sideNav.general()).toBeVisible(); + await expect(sideNav.general).toBeVisible(); }); }); test.describe('[Spotlight search bar]', () => { test('expect show spotlight search bar', async () => { - await sideNav.spotlightSearchIcon().click(); - await expect(sideNav.spotlightSearch()).toBeVisible(); + await sideNav.spotlightSearchIcon.click(); + await expect(sideNav.spotlightSearch).toBeVisible(); }); test('expect click the spotlight and show the channel list', async () => { - await sideNav.spotlightSearch().click(); - await expect(sideNav.spotlightSearchPopUp()).toBeVisible(); + await sideNav.spotlightSearch.click(); + await expect(sideNav.spotlightSearchPopUp).toBeVisible(); }); test('expect add text to the spotlight and show the channel list', async () => { - await sideNav.spotlightSearch().type('rocket.cat'); - await expect(sideNav.spotlightSearchPopUp()).toBeVisible(); - await sideNav.getPage().locator('//*[@data-qa="sidebar-search-result"]//*[@data-index="0"]').click(); + await sideNav.spotlightSearch.type('rocket.cat'); + await expect(sideNav.spotlightSearchPopUp).toBeVisible(); + await sideNav.page.locator('//*[@data-qa="sidebar-search-result"]//*[@data-index="0"]').click(); }); }); }); test.describe('[User Options]', () => { test.describe('[Render]', () => { test.beforeEach(async () => { - await sideNav.sidebarUserMenu().click(); + await sideNav.sidebarUserMenu.click(); }); test.afterEach(async () => { - await sideNav.sidebarUserMenu().click(); + await sideNav.sidebarUserMenu.click(); }); test('expect show online button', async () => { - await expect(sideNav.statusOnline()).toBeVisible(); + await expect(sideNav.statusOnline).toBeVisible(); }); test('expect show away button', async () => { - await expect(sideNav.statusAway()).toBeVisible(); + await expect(sideNav.statusAway).toBeVisible(); }); test('expect show busy button', async () => { - await expect(sideNav.statusBusy()).toBeVisible(); + await expect(sideNav.statusBusy).toBeVisible(); }); test('expect show offline button', async () => { - await expect(sideNav.statusOffline()).toBeVisible(); + await expect(sideNav.statusOffline).toBeVisible(); }); test('expect show my account button', async () => { - await expect(sideNav.account()).toBeVisible(); + await expect(sideNav.account).toBeVisible(); }); test('expect show logout button', async () => { - await expect(sideNav.logout()).toBeVisible(); + await expect(sideNav.logout).toBeVisible(); }); }); }); @@ -101,42 +98,42 @@ test.describe('[Main Elements Render]', function () { }); test('expect show the empty favorite star (before)', async () => { - await expect(mainContent.emptyFavoriteStar()).toBeVisible(); + await expect(mainContent.emptyFavoriteStar).toBeVisible(); }); test('expect click the empty star', async () => { - await mainContent.emptyFavoriteStar().click(); + await mainContent.emptyFavoriteStar.click(); }); test('expect show the filled favorite star', async () => { - await expect(mainContent.favoriteStar()).toBeVisible(); + await expect(mainContent.favoriteStar).toBeVisible(); }); test('expect click the star', async () => { - await mainContent.favoriteStar().click(); + await mainContent.favoriteStar.click(); }); test('expect show the empty favorite star (after)', async () => { - await expect(mainContent.emptyFavoriteStar()).toBeVisible(); + await expect(mainContent.emptyFavoriteStar).toBeVisible(); }); test('expect show the message input bar', async () => { - await expect(mainContent.messageInput()).toBeVisible(); + await expect(mainContent.messageInput).toBeVisible(); }); test('expect show the message box actions button', async () => { - await expect(mainContent.messageBoxActions()).toBeVisible(); + await expect(mainContent.messageBoxActions).toBeVisible(); }); test('expect show the audio recording button', async () => { - await expect(mainContent.recordBtn()).toBeVisible(); + await expect(mainContent.recordBtn).toBeVisible(); }); test('expect show the emoji button', async () => { - await expect(mainContent.emojiBtn()).toBeVisible(); + await expect(mainContent.emojiBtn).toBeVisible(); }); test('expect not show the Admin tag', async () => { - await expect(mainContent.lastMessageUserTag()).not.toBeVisible(); + await expect(mainContent.lastMessageUserTag).not.toBeVisible(); }); }); }); @@ -157,11 +154,11 @@ test.describe('[Main Elements Render]', function () { }); test('expect show the room info button', async () => { - await expect(flexTab.channelTab()).toBeVisible(); + await expect(flexTab.channelTab).toBeVisible(); }); test('expect show the room info tab content', async () => { - await expect(flexTab.channelSettings()).toBeVisible(); + await expect(flexTab.channelSettings).toBeVisible(); }); }); @@ -175,11 +172,11 @@ test.describe('[Main Elements Render]', function () { }); test('expect show the message search button', async () => { - await expect(flexTab.searchTab()).toBeVisible(); + await expect(flexTab.searchTab).toBeVisible(); }); test('expect show the message tab content', async () => { - await expect(flexTab.searchTabContent()).toBeVisible(); + await expect(flexTab.searchTabContent).toBeVisible(); }); }); @@ -193,11 +190,11 @@ test.describe('[Main Elements Render]', function () { }); test('expect show the members tab button', () => { - expect(flexTab.membersTab().isVisible()).toBeTruthy(); + expect(flexTab.membersTab.isVisible()).toBeTruthy(); }); test('expect show the members content', async () => { - expect(flexTab.membersTabContent().isVisible()).toBeTruthy(); + expect(flexTab.membersTabContent.isVisible()).toBeTruthy(); }); }); @@ -211,11 +208,11 @@ test.describe('[Main Elements Render]', function () { }); test('expect not show the notifications button', async () => { - await expect(flexTab.notificationsTab()).not.toBeVisible(); + await expect(flexTab.notificationsTab).not.toBeVisible(); }); test('expect show the notifications Tab content', async () => { - await expect(flexTab.notificationsSettings()).toBeVisible(); + await expect(flexTab.notificationsSettings).toBeVisible(); }); }); @@ -229,7 +226,7 @@ test.describe('[Main Elements Render]', function () { }); test('expect show the files Tab content', async () => { - await expect(flexTab.filesTabContent()).toBeVisible(); + await expect(flexTab.filesTabContent).toBeVisible(); }); }); @@ -243,7 +240,7 @@ test.describe('[Main Elements Render]', function () { }); test('expect show the mentions Tab content', async () => { - await expect(flexTab.mentionsTabContent()).toBeVisible(); + await expect(flexTab.mentionsTabContent).toBeVisible(); }); }); @@ -257,7 +254,7 @@ test.describe('[Main Elements Render]', function () { }); test('expect show the starred messages Tab content', async () => { - await expect(flexTab.starredTabContent()).toBeVisible(); + await expect(flexTab.starredTabContent).toBeVisible(); }); }); @@ -271,7 +268,7 @@ test.describe('[Main Elements Render]', function () { }); test('expect show the pinned messages Tab content', async () => { - await expect(flexTab.pinnedTabContent()).toBeVisible(); + await expect(flexTab.pinnedTabContent).toBeVisible(); }); }); }); diff --git a/apps/meteor/tests/e2e/05-channel-creation.spec.ts b/apps/meteor/tests/e2e/05-channel-creation.spec.ts index d75a06e288da..8a5ca373d87a 100644 --- a/apps/meteor/tests/e2e/05-channel-creation.spec.ts +++ b/apps/meteor/tests/e2e/05-channel-creation.spec.ts @@ -1,8 +1,7 @@ import { test } from '@playwright/test'; import { faker } from '@faker-js/faker'; -import ChannelCreation from './utils/pageobjects/ChannelCreation'; -import LoginPage from './utils/pageobjects/LoginPage'; +import { LoginPage, ChannelCreation } from './pageobjects'; import { validUserInserted, ROCKET_CAT } from './utils/mocks/userAndPasswordMock'; test.describe('[Channel]', async () => { diff --git a/apps/meteor/tests/e2e/06-messaging.spec.ts b/apps/meteor/tests/e2e/06-messaging.spec.ts index 584d52648d60..694465574c6c 100644 --- a/apps/meteor/tests/e2e/06-messaging.spec.ts +++ b/apps/meteor/tests/e2e/06-messaging.spec.ts @@ -1,13 +1,15 @@ import { expect, test, Browser } from '@playwright/test'; -import MainContent from './utils/pageobjects/MainContent'; -import SideNav from './utils/pageobjects/SideNav'; -import LoginPage from './utils/pageobjects/LoginPage'; -import FlexTab from './utils/pageobjects/FlexTab'; +import { FlexTab, MainContent, SideNav, LoginPage } from './pageobjects'; import { adminLogin, validUserInserted } from './utils/mocks/userAndPasswordMock'; -import { ChatContext } from './utils/types/ChatContext'; -const createBrowserContextForChat = async (browser: Browser, baseURL: string): Promise => { +const createBrowserContextForChat = async ( + browser: Browser, + baseURL: string, +): Promise<{ + mainContent: MainContent; + sideNav: SideNav; +}> => { const page = await browser.newPage(); const loginPage = new LoginPage(page); @@ -40,22 +42,25 @@ test.describe('[Messaging]', () => { }); test.describe('[Normal messaging]', async () => { - let anotherContext: ChatContext; + let anotherContext: { + mainContent: MainContent; + sideNav: SideNav; + }; test.describe('[General channel]', async () => { test.beforeAll(async ({ browser, baseURL }) => { anotherContext = await createBrowserContextForChat(browser, baseURL as string); - await anotherContext.sideNav.general().click(); + await anotherContext.sideNav.general.click(); await anotherContext.mainContent.sendMessage('Hello'); - await sideNav.general().click(); + await sideNav.general.click(); await mainContent.sendMessage('Hello'); }); test.afterAll(async () => { - await anotherContext.mainContent.getPage().close(); + await anotherContext.mainContent.page.close(); }); test('expect received message is visible for two context', async () => { - const anotherUserMessage = mainContent.getPage().locator('li.message[data-own="false"]').last(); - const mainUserMessage = anotherContext.mainContent.getPage().locator('li.message[data-own="false"]').last(); + const anotherUserMessage = mainContent.page.locator('li.message[data-own="false"]').last(); + const mainUserMessage = anotherContext.mainContent.page.locator('li.message[data-own="false"]').last(); await expect(anotherUserMessage).toBeVisible(); await expect(mainUserMessage).toBeVisible(); @@ -70,11 +75,11 @@ test.describe('[Messaging]', () => { await mainContent.sendMessage('Hello'); }); test.afterAll(async () => { - await anotherContext.mainContent.getPage().close(); + await anotherContext.mainContent.page.close(); }); test('expect received message is visible for two context', async () => { - const anotherUserMessage = mainContent.getPage().locator('li.message[data-own="false"]').last(); - const mainUserMessage = anotherContext.mainContent.getPage().locator('li.message[data-own="false"]').last(); + const anotherUserMessage = mainContent.page.locator('li.message[data-own="false"]').last(); + const mainUserMessage = anotherContext.mainContent.page.locator('li.message[data-own="false"]').last(); await expect(anotherUserMessage).toBeVisible(); await expect(mainUserMessage).toBeVisible(); @@ -90,11 +95,11 @@ test.describe('[Messaging]', () => { await mainContent.sendMessage('Hello'); }); test.afterAll(async () => { - await anotherContext.mainContent.getPage().close(); + await anotherContext.mainContent.page.close(); }); test('expect received message is visible for two context', async () => { - const anotherUserMessage = mainContent.getPage().locator('li.message[data-own="false"]').last(); - const mainUserMessage = anotherContext.mainContent.getPage().locator('li.message[data-own="false"]').last(); + const anotherUserMessage = mainContent.page.locator('li.message[data-own="false"]').last(); + const mainUserMessage = anotherContext.mainContent.page.locator('li.message[data-own="false"]').last(); await expect(anotherUserMessage).toBeVisible(); await expect(mainUserMessage).toBeVisible(); @@ -110,11 +115,11 @@ test.describe('[Messaging]', () => { await mainContent.sendMessage('Hello'); }); test.afterAll(async () => { - await anotherContext.mainContent.getPage().close(); + await anotherContext.mainContent.page.close(); }); test('expect received message is visible for two context', async () => { - const anotherUserMessage = mainContent.getPage().locator('li.message[data-own="false"]').last(); - const mainUserMessage = anotherContext.mainContent.getPage().locator('li.message[data-own="false"]').last(); + const anotherUserMessage = mainContent.page.locator('li.message[data-own="false"]').last(); + const mainUserMessage = anotherContext.mainContent.page.locator('li.message[data-own="false"]').last(); await expect(anotherUserMessage).toBeVisible(); await expect(mainUserMessage).toBeVisible(); @@ -123,33 +128,33 @@ test.describe('[Messaging]', () => { test.describe('[File Upload]', async () => { test.beforeAll(async () => { - await sideNav.general().click(); + await sideNav.general.click(); }); test.describe('[Render]', async () => { test.beforeAll(async () => { await mainContent.dragAndDropFile(); }); test('expect modal is visible', async () => { - await expect(mainContent.modalTitle()).toHaveText('File Upload'); + await expect(mainContent.modalTitle).toHaveText('File Upload'); }); test('expect cancel button is visible', async () => { - await expect(mainContent.modalCancelButton()).toBeVisible(); + await expect(mainContent.modalCancelButton).toBeVisible(); }); test('expect confirm button is visible', async () => { - await expect(mainContent.buttonSend()).toBeVisible(); + await expect(mainContent.buttonSend).toBeVisible(); }); test('expect file preview is visible', async () => { - await expect(mainContent.modalFilePreview()).toBeVisible(); + await expect(mainContent.modalFilePreview).toBeVisible(); }); test('expect file name input is visible', async () => { - await expect(mainContent.fileName()).toBeVisible(); - await expect(mainContent.fileName()).toHaveText('File name'); + await expect(mainContent.fileName).toBeVisible(); + await expect(mainContent.fileName).toHaveText('File name'); }); test('expect file description is visible', async () => { - await expect(mainContent.fileDescription()).toBeVisible(); - await expect(mainContent.fileDescription()).toHaveText('File description'); + await expect(mainContent.fileDescription).toBeVisible(); + await expect(mainContent.fileDescription).toHaveText('File description'); }); }); test.describe('[Actions]', async () => { @@ -158,24 +163,24 @@ test.describe('[Messaging]', () => { }); test('expect not show modal after click in cancel button', async () => { - await mainContent.modalCancelButton().click(); - await expect(mainContent.modalFilePreview()).not.toBeVisible(); + await mainContent.modalCancelButton.click(); + await expect(mainContent.modalFilePreview).not.toBeVisible(); }); test('expect send file not show modal', async () => { await mainContent.sendFileClick(); - await expect(mainContent.modalFilePreview()).not.toBeVisible(); + await expect(mainContent.modalFilePreview).not.toBeVisible(); }); test('expect send file with description', async () => { await mainContent.setDescription(); await mainContent.sendFileClick(); - await expect(mainContent.getFileDescription()).toHaveText('any_description'); + await expect(mainContent.getFileDescription).toHaveText('any_description'); }); test('expect send file with different file name', async () => { await mainContent.setFileName(); await mainContent.sendFileClick(); - await expect(mainContent.lastMessageFileName()).toContainText('any_file1.txt'); + await expect(mainContent.lastMessageFileName).toContainText('any_file1.txt'); }); }); }); @@ -183,7 +188,7 @@ test.describe('[Messaging]', () => { test.describe('[Messaging actions]', async () => { test.describe('[Usage]', async () => { test.beforeAll(async () => { - await sideNav.general().click(); + await sideNav.general.click(); }); test.describe('[Reply]', async () => { test.beforeAll(async () => { @@ -192,10 +197,10 @@ test.describe('[Messaging]', () => { }); test('expect reply the message', async () => { await mainContent.selectAction('reply'); - await flexTab.messageInput().type('this is a reply message'); + await flexTab.messageInput.type('this is a reply message'); await flexTab.keyboardPress('Enter'); - await expect(flexTab.flexTabViewThreadMessage()).toHaveText('this is a reply message'); - await flexTab.closeThreadMessage().click(); + await expect(flexTab.flexTabViewThreadMessage).toHaveText('this is a reply message'); + await flexTab.closeThreadMessage.click(); }); }); @@ -231,7 +236,7 @@ test.describe('[Messaging]', () => { test('it should quote the message', async () => { await mainContent.selectAction('quote'); - await expect(mainContent.waitForLastMessageTextAttachmentEqualsText()).toHaveText(message); + await expect(mainContent.waitForLastMessageTextAttachmentEqualsText).toHaveText(message); }); }); diff --git a/apps/meteor/tests/e2e/07-emoji.spec.ts b/apps/meteor/tests/e2e/07-emoji.spec.ts index 847e5fa46347..da2bdc52cc17 100644 --- a/apps/meteor/tests/e2e/07-emoji.spec.ts +++ b/apps/meteor/tests/e2e/07-emoji.spec.ts @@ -1,8 +1,6 @@ import { test, expect } from '@playwright/test'; -import MainContent from './utils/pageobjects/MainContent'; -import SideNav from './utils/pageobjects/SideNav'; -import LoginPage from './utils/pageobjects/LoginPage'; +import { SideNav, MainContent, LoginPage } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; test.describe('[Emoji]', function () { @@ -26,89 +24,89 @@ test.describe('[Emoji]', function () { test.describe('Render:', () => { test.beforeAll(async () => { - await mainContent.emojiBtn().click(); + await mainContent.emojiBtn.click(); }); test.afterAll(async () => { - await mainContent.emojiSmile().first().click(); + await mainContent.emojiSmile.first().click(); await mainContent.setTextToInput(''); }); test('expect show the emoji picker menu', async () => { - await expect(mainContent.emojiPickerMainScreen()).toBeVisible(); + await expect(mainContent.emojiPickerMainScreen).toBeVisible(); }); test('expect click the emoji picker people tab', async () => { - await mainContent.emojiPickerPeopleIcon().click(); + await mainContent.emojiPickerPeopleIcon.click(); }); test('expect show the emoji picker people tab', async () => { - await expect(mainContent.emojiPickerPeopleIcon()).toBeVisible(); + await expect(mainContent.emojiPickerPeopleIcon).toBeVisible(); }); test('expect show the emoji picker nature tab', async () => { - await expect(mainContent.emojiPickerNatureIcon()).toBeVisible(); + await expect(mainContent.emojiPickerNatureIcon).toBeVisible(); }); test('expect show the emoji picker food tab', async () => { - await expect(mainContent.emojiPickerFoodIcon()).toBeVisible(); + await expect(mainContent.emojiPickerFoodIcon).toBeVisible(); }); test('expect show the emoji picker activity tab', async () => { - await expect(mainContent.emojiPickerActivityIcon()).toBeVisible(); + await expect(mainContent.emojiPickerActivityIcon).toBeVisible(); }); test('expect show the emoji picker travel tab', async () => { - await expect(mainContent.emojiPickerTravelIcon()).toBeVisible(); + await expect(mainContent.emojiPickerTravelIcon).toBeVisible(); }); test('expect show the emoji picker objects tab', async () => { - await expect(mainContent.emojiPickerObjectsIcon()).toBeVisible(); + await expect(mainContent.emojiPickerObjectsIcon).toBeVisible(); }); test('expect show the emoji picker symbols tab', async () => { - await expect(mainContent.emojiPickerSymbolsIcon()).toBeVisible(); + await expect(mainContent.emojiPickerSymbolsIcon).toBeVisible(); }); test('expect show the emoji picker flags tab', async () => { - await expect(mainContent.emojiPickerFlagsIcon()).toBeVisible(); + await expect(mainContent.emojiPickerFlagsIcon).toBeVisible(); }); test('expect show the emoji picker custom tab', async () => { - await expect(mainContent.emojiPickerCustomIcon()).toBeVisible(); + await expect(mainContent.emojiPickerCustomIcon).toBeVisible(); }); test('expect show the emoji picker change tone button', async () => { - await expect(mainContent.emojiPickerChangeTone()).toBeVisible(); + await expect(mainContent.emojiPickerChangeTone).toBeVisible(); }); test('expect show the emoji picker search bar', async () => { - await expect(mainContent.emojiPickerFilter()).toBeVisible(); + await expect(mainContent.emojiPickerFilter).toBeVisible(); }); }); test.describe('[Usage]', () => { test.describe('send emoji via screen:', () => { test.beforeAll(async () => { - await mainContent.emojiBtn().click(); - await mainContent.emojiPickerPeopleIcon().click(); + await mainContent.emojiBtn.click(); + await mainContent.emojiPickerPeopleIcon.click(); }); test('expect select a grinning emoji', async () => { - await mainContent.emojiGrinning().first().click(); + await mainContent.emojiGrinning.first().click(); }); test('expect be that the value on the message input is the same as the emoji clicked', async () => { - await expect(mainContent.messageInput()).toHaveValue(':grinning: '); + await expect(mainContent.messageInput).toHaveValue(':grinning: '); }); test('expect send the emoji', async () => { await mainContent.addTextToInput(' '); - await mainContent.getPage().keyboard.press('Enter'); + await mainContent.page.keyboard.press('Enter'); }); test('expect be that the value on the message is the same as the emoji clicked', async () => { - await expect(mainContent.lastMessage()).toContainText('😀'); + await expect(mainContent.lastMessage).toContainText('😀'); }); }); @@ -118,31 +116,31 @@ test.describe('[Emoji]', function () { }); test('expect show the emoji popup bar', async () => { - await expect(mainContent.messagePopUp()).toBeVisible(); + await expect(mainContent.messagePopUp).toBeVisible(); }); test('expect be that the emoji popup bar title is emoji', async () => { - await expect(mainContent.messagePopUpTitle()).toContainText('Emoji'); + await expect(mainContent.messagePopUpTitle).toContainText('Emoji'); }); test('expect show the emoji popup bar items', async () => { - await expect(mainContent.messagePopUpItems()).toBeVisible(); + await expect(mainContent.messagePopUpItems).toBeVisible(); }); test('expect click the first emoji on the popup list', async () => { - await mainContent.messagePopUpFirstItem().click(); + await mainContent.messagePopUpFirstItem.click(); }); test('expect be that the value on the message input is the same as the emoji clicked', async () => { - await expect(mainContent.messageInput()).toHaveValue(':smiley: '); + await expect(mainContent.messageInput).toHaveValue(':smiley: '); }); test('expect send the emoji', async () => { - await mainContent.sendBtn().click(); + await mainContent.sendBtn.click(); }); test('expect be that the value on the message is the same as the emoji clicked', async () => { - await expect(mainContent.lastMessage()).toContainText('😃'); + await expect(mainContent.lastMessage).toContainText('😃'); }); }); diff --git a/apps/meteor/tests/e2e/08-resolutions.spec.ts b/apps/meteor/tests/e2e/08-resolutions.spec.ts index c1197a1814a5..d7e5dd9648e1 100644 --- a/apps/meteor/tests/e2e/08-resolutions.spec.ts +++ b/apps/meteor/tests/e2e/08-resolutions.spec.ts @@ -1,9 +1,6 @@ import { test, expect, Browser } from '@playwright/test'; -import MainContent from './utils/pageobjects/MainContent'; -import SideNav from './utils/pageobjects/SideNav'; -import LoginPage from './utils/pageobjects/LoginPage'; -import Global from './utils/pageobjects/Global'; +import { Global, MainContent, SideNav, LoginPage } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; let loginPage: LoginPage; @@ -38,11 +35,11 @@ test.describe('[Resolution]', function () { test.afterAll(async ({ browser, baseURL }) => { await initConfig(browser, baseURL, { viewport: { width: 1600, height: 1600 } }); - await expect(sideNav.spotlightSearchIcon()).toBeVisible(); + await expect(sideNav.spotlightSearchIcon).toBeVisible(); }); test('expect close the sidenav', async () => { - const position = await mainContent.mainContent().boundingBox(); + const position = await mainContent.mainContent.boundingBox(); await expect(position?.x).toEqual(0); await expect(await sideNav.isSideBarOpen()).toBeFalsy; }); @@ -50,25 +47,25 @@ test.describe('[Resolution]', function () { test.describe('moving elements:', async () => { test.beforeEach(async () => { if (!(await sideNav.isSideBarOpen())) { - await sideNav.burgerBtn().click({ force: true }); + await sideNav.burgerBtn.click({ force: true }); } }); test('expect open the sidenav', async () => { - const position = await mainContent.mainContent().boundingBox(); + const position = await mainContent.mainContent.boundingBox(); await expect(position?.x).toEqual(0); await expect(await sideNav.isSideBarOpen()).toBeTruthy; }); test('expect not close sidebar on pressing the sidebar item menu', async () => { - await sideNav.firstSidebarItemMenu().click(); + await sideNav.firstSidebarItemMenu.click(); - const position = await mainContent.mainContent().boundingBox(); + const position = await mainContent.mainContent.boundingBox(); await expect(position?.x).toEqual(0); await expect(await sideNav.isSideBarOpen()).toBeTruthy; - await sideNav.firstSidebarItemMenu().click(); + await sideNav.firstSidebarItemMenu.click(); }); test('expect close the sidenav when open general channel', async () => { @@ -79,33 +76,33 @@ test.describe('[Resolution]', function () { test.describe('Preferences', async () => { test.beforeAll(async () => { if (!(await sideNav.isSideBarOpen())) { - await sideNav.burgerBtn().click({ force: true }); + await sideNav.burgerBtn.click({ force: true }); } - await sideNav.sidebarUserMenu().click(); - await sideNav.account().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.account.click(); }); test.afterEach(async () => { - await sideNav.returnToMenuInLowResolution().click(); + await sideNav.returnToMenuInLowResolution.click(); }); test('expect close the sidenav when press the preferences link', async () => { - await sideNav.preferences().click(); - await sideNav.getPage().mouse.click(640, 30); - await expect(await global.flexNav().isVisible()).toBeFalsy; + await sideNav.preferences.click(); + await sideNav.page.mouse.click(640, 30); + await expect(await global.flexNav.isVisible()).toBeFalsy; }); test('expect close the sidenav when press the profile link', async () => { - await sideNav.profile().click(); - await sideNav.getPage().mouse.click(640, 30); - await expect(await sideNav.flexNav().isVisible()).toBeFalsy; + await sideNav.profile.click(); + await sideNav.page.mouse.click(640, 30); + await expect(await sideNav.flexNav.isVisible()).toBeFalsy; }); test('expect close the preferences nav', async () => { - await sideNav.preferencesClose().click(); - await sideNav.getPage().mouse.click(640, 30); - await expect(await sideNav.flexNav().isVisible()).toBeTruthy; + await sideNav.preferencesClose.click(); + await sideNav.page.mouse.click(640, 30); + await expect(await sideNav.flexNav.isVisible()).toBeTruthy; }); }); }); diff --git a/apps/meteor/tests/e2e/09-channel.spec.ts b/apps/meteor/tests/e2e/09-channel.spec.ts index 555d6e3ce464..cc5d989e98bf 100644 --- a/apps/meteor/tests/e2e/09-channel.spec.ts +++ b/apps/meteor/tests/e2e/09-channel.spec.ts @@ -1,10 +1,6 @@ import { test, expect } from '@playwright/test'; -import FlexTab from './utils/pageobjects/FlexTab'; -import MainContent from './utils/pageobjects/MainContent'; -import SideNav from './utils/pageobjects/SideNav'; -import LoginPage from './utils/pageobjects/LoginPage'; -import Global from './utils/pageobjects/Global'; +import { Global, FlexTab, MainContent, SideNav, LoginPage } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; import { LOCALHOST } from './utils/mocks/urlMock'; import { publicChannelCreated, setPublicChannelCreated } from './utils/mocks/checks'; @@ -43,7 +39,7 @@ test.describe('[Channel]', () => { test.describe('[SpotlightSearch]', async () => { test.describe('general:', () => { test('expect search general', async () => { - await sideNav.spotlightSearchIcon().click(); + await sideNav.spotlightSearchIcon.click(); await sideNav.searchChannel('general'); }); @@ -55,7 +51,7 @@ test.describe('[Channel]', () => { test.describe('user created channel:', () => { test('expect search the user created channel', async () => { - await sideNav.spotlightSearchIcon().click(); + await sideNav.spotlightSearchIcon.click(); await sideNav.searchChannel(publicChannelName); }); @@ -68,7 +64,7 @@ test.describe('[Channel]', () => { test.describe('[SideNav Channel List]', () => { test.beforeAll(async () => { - await mainContent.messageInput().click(); + await mainContent.messageInput.click(); }); test.describe('general:', async () => { @@ -102,14 +98,14 @@ test.describe('[Channel]', () => { test.describe('Adding a user to the room:', async () => { test.beforeAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } await flexTab.operateFlexTab('members', true); }); test.afterAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } await flexTab.operateFlexTab('members', false); @@ -118,7 +114,7 @@ test.describe('[Channel]', () => { test('expect add people to the room', async () => { await flexTab.addPeopleToChannel(targetUser); hasUserAddedInChannel = true; - await expect(global.getToastBarSuccess()).toBeVisible(); + await expect(global.getToastBarSuccess).toBeVisible(); }); }); @@ -126,24 +122,24 @@ test.describe('[Channel]', () => { test.describe('[Channel topic edit]', async () => { test.beforeAll(async () => { await flexTab.operateFlexTab('info', true); - await flexTab.editNameBtn().click(); + await flexTab.editNameBtn.click(); }); test.afterAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - if (await flexTab.mainSideBar().isVisible()) { - await flexTab.mainSideBarClose().click(); + if (await flexTab.mainSideBar.isVisible()) { + await flexTab.mainSideBarClose.click(); } }); test('expect edit the topic input', async () => { - await flexTab.editTopicTextInput().fill('TOPIC EDITED'); + await flexTab.editTopicTextInput.fill('TOPIC EDITED'); }); test('expect save the topic', async () => { - await flexTab.editNameSave().click(); + await flexTab.editNameSave.click(); }); test('expect show the new topic', async () => { @@ -154,57 +150,57 @@ test.describe('[Channel]', () => { test.describe('[Channel announcement edit]', async () => { test.beforeAll(async () => { await flexTab.operateFlexTab('info', true); - await flexTab.editNameBtn().click(); + await flexTab.editNameBtn.click(); }); test.afterAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - if (await flexTab.mainSideBar().isVisible()) { - await flexTab.mainSideBarClose().click(); + if (await flexTab.mainSideBar.isVisible()) { + await flexTab.mainSideBarClose.click(); } }); test('expect edit the announcement input', async () => { - await flexTab.editAnnouncementTextInput().type('ANNOUNCEMENT EDITED'); + await flexTab.editAnnouncementTextInput.type('ANNOUNCEMENT EDITED'); }); test('expect save the announcement', async () => { - await flexTab.editNameSave().click(); + await flexTab.editNameSave.click(); }); test('expect show the new announcement', async () => { - await expect(flexTab.thirdSetting()).toHaveText('ANNOUNCEMENT EDITED'); + await expect(flexTab.thirdSetting).toHaveText('ANNOUNCEMENT EDITED'); }); }); test.describe('[Channel description edit]', async () => { test.beforeAll(async () => { await flexTab.operateFlexTab('info', true); - await flexTab.editNameBtn().click(); + await flexTab.editNameBtn.click(); }); test.afterAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - if (await flexTab.mainSideBar().isVisible()) { - await flexTab.mainSideBarClose().click(); + if (await flexTab.mainSideBar.isVisible()) { + await flexTab.mainSideBarClose.click(); } }); test('expect edit the description input', async () => { - await flexTab.editDescriptionTextInput().type('DESCRIPTION EDITED'); + await flexTab.editDescriptionTextInput.type('DESCRIPTION EDITED'); }); test('expect save the description', async () => { - await flexTab.editNameSave().click(); + await flexTab.editNameSave.click(); }); test('expect show the new description', async () => { - await flexTab.mainSideBarBack().click(); - await expect(flexTab.fourthSetting()).toHaveText('DESCRIPTION EDITED'); + await flexTab.mainSideBarBack.click(); + await expect(flexTab.fourthSetting).toHaveText('DESCRIPTION EDITED'); }); }); }); @@ -221,7 +217,7 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } await flexTab.operateFlexTab('members', false); @@ -243,7 +239,7 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } await flexTab.operateFlexTab('members', false); @@ -254,17 +250,17 @@ test.describe('[Channel]', () => { }); test('expect dismiss the toast', async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } }); test('expect the last message should be a subscription role added', async () => { - await expect(mainContent.lastMessageRoleAdded()).toBeVisible(); + await expect(mainContent.lastMessageRoleAdded).toBeVisible(); }); test('expect show the target username in owner add message', async () => { - await expect(mainContent.lastMessageRoleAdded()).toContainText(targetUser); + await expect(mainContent.lastMessageRoleAdded).toContainText(targetUser); }); }); @@ -279,7 +275,7 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } await flexTab.operateFlexTab('members', false); @@ -290,42 +286,42 @@ test.describe('[Channel]', () => { }); test('expect be that the last message is a subscription role added', async () => { - await expect(mainContent.lastMessageRoleAdded()).toBeVisible(); + await expect(mainContent.lastMessageRoleAdded).toBeVisible(); }); }); test.describe('Channel name edit', async () => { test.beforeAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } await flexTab.operateFlexTab('info', true); }); test.afterAll(async () => { - if (await global.getToastBar().isVisible()) { + if (await global.getToastBar.isVisible()) { await global.dismissToastBar(); } - if (await flexTab.mainSideBar().isVisible()) { + if (await flexTab.mainSideBar.isVisible()) { await flexTab.operateFlexTab('info', false); } }); test('expect show the old name', async () => { - await expect(flexTab.firstSetting()).toHaveText(publicChannelName); + await expect(flexTab.firstSetting).toHaveText(publicChannelName); }); test('expect click the edit name', async () => { - await flexTab.editNameBtn().click(); + await flexTab.editNameBtn.click(); }); test('expect edit the name input', async () => { - await flexTab.editNameTextInput().fill(`NAME-EDITED-${publicChannelName}`); + await flexTab.editNameTextInput.fill(`NAME-EDITED-${publicChannelName}`); }); test('expect save the name', async () => { - await flexTab.editNameSave().click(); + await flexTab.editNameSave.click(); }); test('expect show the new name', async () => { diff --git a/apps/meteor/tests/e2e/10-user-preferences.spec.ts b/apps/meteor/tests/e2e/10-user-preferences.spec.ts index 69df180bee2f..864cbcd6c238 100644 --- a/apps/meteor/tests/e2e/10-user-preferences.spec.ts +++ b/apps/meteor/tests/e2e/10-user-preferences.spec.ts @@ -1,11 +1,7 @@ import { test, expect } from '@playwright/test'; import faker from '@faker-js/faker'; -import MainContent from './utils/pageobjects/MainContent'; -import SideNav from './utils/pageobjects/SideNav'; -import LoginPage from './utils/pageobjects/LoginPage'; -import FlexTab from './utils/pageobjects/FlexTab'; -import PreferencesMainContent from './utils/pageobjects/PreferencesMainContent'; +import { PreferencesMainContent, MainContent, SideNav, LoginPage, FlexTab } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; import { clearMessages } from './utils/helpers/clearMessages'; import { verifyTestBaseUrl } from './utils/configs/verifyTestBaseUrl'; @@ -37,51 +33,51 @@ test.describe('[User Preferences]', () => { preferencesMainContent = new PreferencesMainContent(page); flexTab = new FlexTab(page); - await sideNav.sidebarUserMenu().click(); - await sideNav.account().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.account.click(); }); test.describe('render:', () => { test('expect show the preferences link', async () => { - await expect(sideNav.preferences()).toBeVisible(); + await expect(sideNav.preferences).toBeVisible(); }); test('expect show the profile link', async () => { - await expect(sideNav.profile()).toBeVisible(); + await expect(sideNav.profile).toBeVisible(); }); test('expect click on the profile link', async () => { - await sideNav.profile().click(); + await sideNav.profile.click(); }); test('expect show the username input', async () => { - await expect(preferencesMainContent.userNameTextInput()).toBeVisible(); + await expect(preferencesMainContent.userNameTextInput).toBeVisible(); }); test('expect show the real name input', async () => { - await expect(preferencesMainContent.realNameTextInput()).toBeVisible(); + await expect(preferencesMainContent.realNameTextInput).toBeVisible(); }); test('expect show the email input', async () => { - await expect(preferencesMainContent.emailTextInput()).toBeVisible(); // .scrollIntoView() + await expect(preferencesMainContent.emailTextInput).toBeVisible(); // .scrollIntoView() }); test('expect show the password input', async () => { - await expect(preferencesMainContent.passwordTextInput()).toBeVisible(); // .scrollIntoView() + await expect(preferencesMainContent.passwordTextInput).toBeVisible(); // .scrollIntoView() }); test('expect show the submit button', async () => { - await expect(preferencesMainContent.submitBtn()).toBeVisible(); - await expect(preferencesMainContent.submitBtn()).toBeDisabled(); + await expect(preferencesMainContent.submitBtn).toBeVisible(); + await expect(preferencesMainContent.submitBtn).toBeDisabled(); }); }); test.describe('[User Info Change]', () => { const newName = faker.name.findName(); - const newUserName = `${faker.name.lastName()}${faker.name.firstName()}`; + const newUserName = `${faker.internet.userName(newName)}`; test('expect click on the profile link', async () => { - await sideNav.profile().click(); + await sideNav.profile.click(); }); test('expect change the name field', async () => { @@ -97,7 +93,7 @@ test.describe('[User Preferences]', () => { }); test('expect close the preferences menu', async () => { - await sideNav.preferencesClose().click(); + await sideNav.preferencesClose.click(); await sideNav.getChannelFromList('general').scrollIntoViewIfNeeded(); await sideNav.getChannelFromList('general').click(); }); @@ -107,17 +103,17 @@ test.describe('[User Preferences]', () => { }); test('expect be that the name on the last message is the edited one', async () => { - await expect(mainContent.lastMessageUser()).toContainText(newUserName); + await expect(mainContent.lastMessageUser).toContainText(newUserName); }); test('expect be that the user name on the members flex tab is the edited one', async () => { - await mainContent.lastMessageUser().click(); - await expect(mainContent.userCard()).toBeVisible(); + await mainContent.lastMessageUser.click(); + await expect(mainContent.userCard).toBeVisible(); }); test('expect that the real name on the members flex tab is the edited one', async () => { - await mainContent.viewUserProfile().click(); - await expect(flexTab.memberRealName()).toHaveText(newUserName); + await mainContent.viewUserProfile.click(); + await expect(flexTab.memberRealName).toHaveText(newUserName); }); }); }); diff --git a/apps/meteor/tests/e2e/11-admin.spec.ts b/apps/meteor/tests/e2e/11-admin.spec.ts index 450aa0e2408b..95ed3527df92 100644 --- a/apps/meteor/tests/e2e/11-admin.spec.ts +++ b/apps/meteor/tests/e2e/11-admin.spec.ts @@ -1,10 +1,7 @@ import { test, expect } from '@playwright/test'; -import LoginPage from './utils/pageobjects/LoginPage'; -import SideNav from './utils/pageobjects/SideNav'; import { adminLogin, ROCKET_CAT } from './utils/mocks/userAndPasswordMock'; -import Administration from './utils/pageobjects/Administration'; -import FlexTab from './utils/pageobjects/FlexTab'; +import { FlexTab, Administration, LoginPage, SideNav } from './pageobjects'; import { ROCKET_CAT_SELECTOR } from './utils/mocks/waitSelectorsMock'; import { Checkbox } from './utils/enums/Checkbox'; @@ -26,463 +23,435 @@ test.describe('[Administration]', () => { }); test.describe('[Admin View]', () => { test.beforeAll(async () => { - await sideNav.sidebarUserMenu().click(); - await sideNav.admin().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.admin.click(); }); test.describe('[Info]', () => { test('expect admin page is showed', async () => { - await admin.infoLink().click(); - await expect(admin.infoDeployment()).toBeVisible(); - await expect(admin.infoLicense()).toBeVisible(); - await expect(admin.infoUsage()).toBeVisible(); - await expect(admin.infoFederation()).toBeVisible(); + await admin.infoLink.click(); + await expect(admin.infoDeployment).toBeVisible(); + await expect(admin.infoLicense).toBeVisible(); + await expect(admin.infoUsage).toBeVisible(); + await expect(admin.infoFederation).toBeVisible(); }); }); test.describe('[Rooms]', () => { test.beforeAll(async () => { - await admin.roomsLink().click(); + await admin.roomsLink.click(); }); test.afterAll(async () => { - await admin.infoLink().click(); + await admin.infoLink.click(); }); test.describe('[Render]', () => { test('expect rom page is rendered is rendered', async () => { await admin.verifyCheckBoxRendered(checkBoxesSelectors); - await expect(admin.roomsSearchForm()).toBeVisible(); + await expect(admin.roomsSearchForm).toBeVisible(); }); }); test.describe('[Filter search input]', () => { test.beforeAll(async () => { - await admin.roomsSearchForm().click(); + await admin.roomsSearchForm.click(); }); test.afterAll(async () => { - await admin.roomsSearchForm().click({ clickCount: 3 }); + await admin.roomsSearchForm.click({ clickCount: 3 }); await admin.keyboardPress('Backspace'); }); test('expect show the general channel', async () => { - await admin.roomsSearchForm().type('general'); - await expect(admin.roomsGeneralChannel()).toBeVisible(); + await admin.roomsSearchForm.type('general'); + await expect(admin.roomsGeneralChannel).toBeVisible(); }); test('expect dont show rooms when room dont exist', async () => { - await admin.roomsSearchForm().type('any_room'); - await expect(admin.notFoundChannelOrUser()).toBeVisible(); + await admin.roomsSearchForm.type('any_room'); + await expect(admin.notFoundChannelOrUser).toBeVisible(); }); }); test.describe('[Filter checkbox]', () => { test.beforeAll(async () => { - await admin.roomsSearchForm().click({ clickCount: 3 }); + await admin.roomsSearchForm.click({ clickCount: 3 }); await admin.keyboardPress('Backspace'); }); test('expect not show the general channel with direct', async () => { await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Direct]).click(); - await admin.roomsGeneralChannel().waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel()).not.toBeVisible(); + await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(admin.roomsGeneralChannel).not.toBeVisible(); await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Direct]).click(); }); test('expect show the general channel with public ', async () => { await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Public]).click(); - await admin.roomsGeneralChannel().waitFor({ state: 'visible' }); - await expect(admin.roomsGeneralChannel()).toBeVisible(); + await admin.roomsGeneralChannel.waitFor({ state: 'visible' }); + await expect(admin.roomsGeneralChannel).toBeVisible(); await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Public]).click(); }); test('expect not show the general channel with private ', async () => { await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Private]).click(); - await admin.roomsGeneralChannel().waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel()).not.toBeVisible(); + await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(admin.roomsGeneralChannel).not.toBeVisible(); await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Private]).click(); }); test('expect not show the general channel with omnichannel', async () => { await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Omnichannel]).click(); - await admin.roomsGeneralChannel().waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel()).not.toBeVisible(); + await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(admin.roomsGeneralChannel).not.toBeVisible(); await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Omnichannel]).click(); }); test('expect not show the general channel with discussion', async () => { await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Discussions]).click(); - await admin.roomsGeneralChannel().waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel()).not.toBeVisible(); + await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(admin.roomsGeneralChannel).not.toBeVisible(); await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Discussions]).click(); }); test('expect not show the general channel with teams', async () => { await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Teams]).click(); - await admin.roomsGeneralChannel().waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel()).not.toBeVisible(); + await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(admin.roomsGeneralChannel).not.toBeVisible(); await admin.adminCheckBox(checkBoxesSelectors[Checkbox.Teams]).click(); }); }); test.describe('[Users]', () => { test.beforeAll(async () => { - await admin.usersLink().click(); + await admin.usersLink.click(); }); test.afterAll(async () => { - await admin.infoLink().click(); + await admin.infoLink.click(); }); test.describe('[Filter text]', async () => { test.beforeEach(async () => { - await admin.usersFilter().click(); + await admin.usersFilter.click(); }); test.afterAll(async () => { - await admin.usersFilter().click(); - await admin.usersFilter().type(''); + await admin.usersFilter.click(); + await admin.usersFilter.type(''); }); test('expect should show rocket.cat', async () => { - await admin.usersFilter().type(ROCKET_CAT); + await admin.usersFilter.type(ROCKET_CAT); await admin.waitForSelector(ROCKET_CAT_SELECTOR); }); test('expect dont user when write wrong name', async () => { - await admin.usersFilter().type('any_user_wrong'); - await expect(admin.notFoundChannels()).toBeVisible(); + await admin.usersFilter.type('any_user_wrong'); + await expect(admin.notFoundChannels).toBeVisible(); }); }); test.describe('[Create user]', () => { test.beforeAll(async () => { - await flexTab.usersAddUserTab().click(); + await flexTab.usersAddUserTab.click(); }); test('expect tab user add is rendering', async () => { - await expect(flexTab.usersAddUserName()).toBeVisible(); - await expect(flexTab.usersAddUserUsername()).toBeVisible(); - await expect(flexTab.usersAddUserEmail()).toBeVisible(); - await expect(flexTab.usersAddUserVerifiedCheckbox()).toBeVisible(); - await expect(flexTab.usersAddUserPassword()).toBeVisible(); - await expect(flexTab.usersAddUserRandomPassword()).toBeVisible(); - await expect(flexTab.usersAddUserChangePasswordCheckbox()).toBeVisible(); - await expect(flexTab.usersAddUserRoleList()).toBeVisible(); - await expect(flexTab.usersAddUserDefaultChannelCheckbox()).toBeVisible(); - await expect(flexTab.usersAddUserWelcomeEmailCheckbox()).toBeVisible(); - await expect(flexTab.usersButtonSave()).toBeVisible(); - await expect(flexTab.usersButtonCancel()).toBeVisible(); - - await flexTab.usersAddUserTabClose().waitFor(); - await flexTab.usersAddUserTabClose().click(); - - await expect(flexTab.addUserTable()).not.toBeVisible(); + await expect(flexTab.usersAddUserName).toBeVisible(); + await expect(flexTab.usersAddUserUsername).toBeVisible(); + await expect(flexTab.usersAddUserEmail).toBeVisible(); + await expect(flexTab.usersAddUserVerifiedCheckbox).toBeVisible(); + await expect(flexTab.usersAddUserPassword).toBeVisible(); + await expect(flexTab.usersAddUserRandomPassword).toBeVisible(); + await expect(flexTab.usersAddUserChangePasswordCheckbox).toBeVisible(); + await expect(flexTab.usersAddUserRoleList).toBeVisible(); + await expect(flexTab.usersAddUserDefaultChannelCheckbox).toBeVisible(); + await expect(flexTab.usersAddUserWelcomeEmailCheckbox).toBeVisible(); + await expect(flexTab.usersButtonSave).toBeVisible(); + await expect(flexTab.usersButtonCancel).toBeVisible(); + + await flexTab.usersAddUserTabClose.waitFor(); + await flexTab.usersAddUserTabClose.click(); + + await expect(flexTab.addUserTable).not.toBeVisible(); }); }); }); }); - // TODO verify how is make o invite - // describe('[Flex Tab] ', () => { - // describe('send invitation:', () => { - // before(() => { - // flexTab.usersSendInvitationTab.waitForVisible(5000); - // flexTab.usersSendInvitationTab.click(); - // flexTab.usersSendInvitationTextArea.waitForVisible(5000); - // }); - - // after(() => { - // flexTab.usersSendInvitationTab.waitForVisible(5000); - // flexTab.usersSendInvitationTab.click(); - // flexTab.usersSendInvitationTextArea.waitForVisible(5000, true); - // }); - - // test('it should show the send invitation text area', () => { - // flexTab.usersSendInvitationTextArea.should('be.visible'); - // }); - - // it('it should show the cancel button', () => { - // flexTab.usersButtonCancel.should('be.visible'); - // }); - - // it('it should show the send button', () => { - // flexTab.usersSendInvitationSend.should('be.visible'); - // }); - // }); - test.describe('[General Settings]', () => { test.beforeAll(async () => { - await admin.settingsLink().click(); - await admin.settingsSearch().type('general'); - await admin.generalSettingsButton().click(); + await admin.settingsLink.click(); + await admin.settingsSearch.type('general'); + await admin.generalSettingsButton.click(); }); test.describe('[General]', () => { test('expect change site url reset button is showed', async () => { - await admin.generalSiteUrl().type('something'); - await expect(admin.generalSiteUrlReset()).toBeVisible(); + await admin.generalSiteUrl.type('something'); + await expect(admin.generalSiteUrlReset).toBeVisible(); }); test('expect change site name reset button is showed', async () => { - await admin.generalSiteName().type('something'); - await expect(admin.generalSiteNameReset()).toBeVisible(); + await admin.generalSiteName.type('something'); + await expect(admin.generalSiteNameReset).toBeVisible(); }); test('expect show language field', async () => { - await expect(admin.generalLanguage()).toBeVisible(); + await expect(admin.generalLanguage).toBeVisible(); }); test('expect aloow invalid self-signed certs reset button is showed', async () => { - await admin.generalSelfSignedCerts().click(); - await expect(admin.generalSelfSignedCertsReset()).toBeVisible(); - await admin.generalSelfSignedCerts().click(); - await expect(admin.generalSelfSignedCertsReset()).not.toBeVisible(); + await admin.generalSelfSignedCerts.click(); + await expect(admin.generalSelfSignedCertsReset).toBeVisible(); + await admin.generalSelfSignedCerts.click(); + await expect(admin.generalSelfSignedCertsReset).not.toBeVisible(); }); test('expect reset enable favorite room is showed', async () => { - await admin.generalFavoriteRoom().click(); - await expect(admin.generalFavoriteRoomReset()).toBeVisible(); - await admin.generalFavoriteRoomReset().click(); - await expect(admin.generalFavoriteRoomReset()).not.toBeVisible(); + await admin.generalFavoriteRoom.click(); + await expect(admin.generalFavoriteRoomReset).toBeVisible(); + await admin.generalFavoriteRoomReset.click(); + await expect(admin.generalFavoriteRoomReset).not.toBeVisible(); }); test('expect CDN prefix reset not show after reset', async () => { - await admin.generalCdnPrefix().type('something'); - await expect(admin.generalCdnPrefixReset()).toBeVisible(); - await admin.generalCdnPrefixReset().click(); - await expect(admin.generalCdnPrefixReset()).not.toBeVisible(); + await admin.generalCdnPrefix.type('something'); + await expect(admin.generalCdnPrefixReset).toBeVisible(); + await admin.generalCdnPrefixReset.click(); + await expect(admin.generalCdnPrefixReset).not.toBeVisible(); }); test('expect SSL reset not showing after reset', async () => { - await admin.generalForceSSL().click(); - await expect(admin.generalForceSSLReset()).toBeVisible(); - await admin.generalForceSSLReset().click(); - await expect(admin.generalForceSSLReset()).not.toBeVisible(); + await admin.generalForceSSL.click(); + await expect(admin.generalForceSSLReset).toBeVisible(); + await admin.generalForceSSLReset.click(); + await expect(admin.generalForceSSLReset).not.toBeVisible(); }); test('expect google tag reset is not visible after reset', async () => { - await admin.generalGoogleTagId().type('something'); - await expect(admin.generalGoogleTagIdReset()).toBeVisible(); - await admin.generalGoogleTagIdReset().click(); - await expect(admin.generalGoogleTagIdReset()).not.toBeVisible(); + await admin.generalGoogleTagId.type('something'); + await expect(admin.generalGoogleTagIdReset).toBeVisible(); + await admin.generalGoogleTagIdReset.click(); + await expect(admin.generalGoogleTagIdReset).not.toBeVisible(); }); test('expect when change bugsnag API Key dont show reset button after reset', async () => { - await admin.generalBugsnagKey().type('something'); - await expect(admin.generalBugsnagKeyReset()).toBeVisible(); - await admin.generalBugsnagKeyReset().click(); - await expect(admin.generalBugsnagKeyReset()).not.toBeVisible(); + await admin.generalBugsnagKey.type('something'); + await expect(admin.generalBugsnagKeyReset).toBeVisible(); + await admin.generalBugsnagKeyReset.click(); + await expect(admin.generalBugsnagKeyReset).not.toBeVisible(); }); test('expect when change Robots dont show reset button after reset', async () => { - await admin.robotsFileContents().type('aa'); - await expect(admin.robotsFileContentsReset()).toBeVisible(); - await admin.robotsFileContentsReset().click(); - await expect(admin.robotsFileContentsReset()).not.toBeVisible(); + await admin.robotsFileContents.type('aa'); + await expect(admin.robotsFileContentsReset).toBeVisible(); + await admin.robotsFileContentsReset.click(); + await expect(admin.robotsFileContentsReset).not.toBeVisible(); }); test('expect when change Default Referrer Policy dont show reset button after reset', async () => { - await admin.defaultReferrerPolicy().click(); - await admin.defaultReferrerPolicyOptions().click(); - await expect(admin.defaultReferrerPolicyReset()).toBeVisible(); - await admin.defaultReferrerPolicyReset().click(); - await expect(admin.defaultReferrerPolicyReset()).not.toBeVisible(); + await admin.defaultReferrerPolicy.click(); + await admin.defaultReferrerPolicyOptions.click(); + await expect(admin.defaultReferrerPolicyReset).toBeVisible(); + await admin.defaultReferrerPolicyReset.click(); + await expect(admin.defaultReferrerPolicyReset).not.toBeVisible(); }); }); test.describe('[Iframe]', () => { test.beforeAll(async () => { - await admin.generalSectionIframeIntegration().click(); + await admin.generalSectionIframeIntegration.click(); }); test('expect iframe integration is rendering', async () => { - await expect(admin.generalIframeSend()).toBeVisible(); - await expect(admin.generalIframeSendTargetOrigin()).toBeVisible(); - await expect(admin.generalIframeReceive()).toBeVisible(); - await expect(admin.generalIframeReceiveOrigin()).toBeVisible(); + await expect(admin.generalIframeSend).toBeVisible(); + await expect(admin.generalIframeSendTargetOrigin).toBeVisible(); + await expect(admin.generalIframeReceive).toBeVisible(); + await expect(admin.generalIframeReceiveOrigin).toBeVisible(); }); }); test.describe('[Notifications]', () => { test.beforeAll(async () => { - await admin.generalSectionNotifications().click(); + await admin.generalSectionNotifications.click(); }); test('expect the max room members field', async () => { - await expect(admin.generalNotificationsMaxRoomMembers()).toBeVisible(); + await expect(admin.generalNotificationsMaxRoomMembers).toBeVisible(); }); }); test.describe('[Rest api]', async () => { test.beforeAll(async () => { - await admin.generalSectionRestApi().click(); + await admin.generalSectionRestApi.click(); }); test('expect show the API user add limit field', async () => { - await expect(admin.generalRestApiUserLimit()).toBeVisible(); + await expect(admin.generalRestApiUserLimit).toBeVisible(); }); }); test.describe('[Reporting]', async () => { test.beforeAll(async () => { - await admin.generalSectionReporting().click(); + await admin.generalSectionReporting.click(); }); test('expect show the report to rocket.chat toggle', async () => { - await expect(admin.generalReporting()).toBeVisible(); + await expect(admin.generalReporting).toBeVisible(); }); }); test.describe('[Stream cast]', async () => { test.beforeAll(async () => { - await admin.generalSectionStreamCast().click(); + await admin.generalSectionStreamCast.click(); }); test('expect show the stream cast address field', async () => { - await expect(admin.generalStreamCastAddress()).toBeVisible(); + await expect(admin.generalStreamCastAddress).toBeVisible(); }); }); test.describe('UTF-8', () => { test.beforeAll(async () => { - await admin.generalSectionUTF8().click(); + await admin.generalSectionUTF8.click(); }); test('expect show the usernames utf8 regex field', async () => { - await expect(admin.generalUTF8UsernamesRegex()).toBeVisible(); + await expect(admin.generalUTF8UsernamesRegex).toBeVisible(); }); test('expect show the channels utf8 regex field', async () => { - await expect(admin.generalUTF8ChannelsRegex()).toBeVisible(); + await expect(admin.generalUTF8ChannelsRegex).toBeVisible(); }); test('expect show the utf8 names slug checkboxes', async () => { - await expect(admin.generalUTF8NamesSlug()).toBeVisible(); + await expect(admin.generalUTF8NamesSlug).toBeVisible(); }); }); }); test.describe('[Accounts]', () => { test.beforeAll(async () => { - await admin.groupSettingsPageBack().click(); - await admin.settingsSearch().type('accounts'); - await admin.accountSettingsButton().click(); + await admin.groupSettingsPageBack.click(); + await admin.settingsSearch.type('accounts'); + await admin.accountSettingsButton.click(); }); test.describe('[Default user preferences]', () => { test.beforeAll(async () => { - await admin.accountsSectionDefaultUserPreferences().click(); + await admin.accountsSectionDefaultUserPreferences.click(); }); test('expect show the enable auto away field', async () => { - await expect(admin.accountsEnableAutoAway()).toBeVisible(); + await expect(admin.accountsEnableAutoAway).toBeVisible(); }); test('the enable auto away field value should be true', async () => { - await admin.accountsEnableAutoAway().check(); + await admin.accountsEnableAutoAway.check(); }); test('expect show the idle timeout limit field', async () => { - await expect(admin.accountsIdleTimeLimit()).toBeVisible(); - const inputValue = await admin.accountsIdleTimeLimit().inputValue(); + await expect(admin.accountsIdleTimeLimit).toBeVisible(); + const inputValue = await admin.accountsIdleTimeLimit.inputValue(); expect(inputValue).toEqual('300'); }); test('expect show desktop audio notifications to be visible', async () => { - await expect(admin.accountsDesktopNotifications()).toBeVisible(); - await expect(admin.accountsDesktopNotifications().locator('.rcx-select__item')).toHaveText('All messages'); + await expect(admin.accountsDesktopNotifications).toBeVisible(); + await expect(admin.accountsDesktopNotifications.locator('.rcx-select__item')).toHaveText('All messages'); }); test('expect show mobile notifications to be visible and option have value', async () => { - await expect(admin.accountsMobileNotifications()).toBeVisible(); - await expect(admin.accountsMobileNotifications().locator('.rcx-select__item')).toHaveText('All messages'); + await expect(admin.accountsMobileNotifications).toBeVisible(); + await expect(admin.accountsMobileNotifications.locator('.rcx-select__item')).toHaveText('All messages'); }); test('expect show the unread tray icon and icon alert field is true', async () => { - await expect(admin.accountsUnreadAlert()).toBeVisible(); - await expect(admin.accountsUnreadAlert().locator('input')).toBeChecked(); + await expect(admin.accountsUnreadAlert).toBeVisible(); + await expect(admin.accountsUnreadAlert.locator('input')).toBeChecked(); }); test('expect show the convert ascii and check is true', async () => { - await expect(admin.accountsConvertAsciiEmoji().locator('input')).toBeVisible(); - await expect(admin.accountsConvertAsciiEmoji().locator('input')).toBeChecked(); + await expect(admin.accountsConvertAsciiEmoji.locator('input')).toBeVisible(); + await expect(admin.accountsConvertAsciiEmoji.locator('input')).toBeChecked(); }); test('expect show message is visible and check is true', async () => { - await expect(admin.accountsAutoImageLoad()).toBeVisible(); - await expect(admin.accountsAutoImageLoad().locator('input')).toBeChecked(); + await expect(admin.accountsAutoImageLoad).toBeVisible(); + await expect(admin.accountsAutoImageLoad.locator('input')).toBeChecked(); }); test('expect show image is visible and check is true', async () => { - await expect(admin.accountsAutoImageLoad()).toBeVisible(); - await expect(admin.accountsAutoImageLoad().locator('input')).toBeChecked(); + await expect(admin.accountsAutoImageLoad).toBeVisible(); + await expect(admin.accountsAutoImageLoad.locator('input')).toBeChecked(); }); test('expect account mobile bandwidth is showed ans check is true', async () => { - await expect(admin.accountsSaveMobileBandwidth()).toBeVisible(); - await expect(admin.accountsSaveMobileBandwidth().locator('input')).toBeVisible(); + await expect(admin.accountsSaveMobileBandwidth).toBeVisible(); + await expect(admin.accountsSaveMobileBandwidth.locator('input')).toBeVisible(); }); test('expect show the collapse embedded media by default field and not be checked', async () => { - await expect(admin.accountsCollapseMediaByDefault()).toBeVisible(); - await expect(admin.accountsCollapseMediaByDefault()).not.toBeChecked(); + await expect(admin.accountsCollapseMediaByDefault).toBeVisible(); + await expect(admin.accountsCollapseMediaByDefault).not.toBeChecked(); }); test('expect show the hide usernames field', async () => { - await expect(admin.accountsHideUsernames()).toBeVisible(); - await expect(admin.accountsHideUsernames()).not.toBeChecked(); + await expect(admin.accountsHideUsernames).toBeVisible(); + await expect(admin.accountsHideUsernames).not.toBeChecked(); }); test('expect show admin hide roles and verify if checked', async () => { - await expect(admin.accountsHideRoles()).toBeVisible(); - await expect(admin.accountsHideRoles()).not.toBeChecked(); + await expect(admin.accountsHideRoles).toBeVisible(); + await expect(admin.accountsHideRoles).not.toBeChecked(); }); test('expect show the hide right sidebar with click field and not checked', async () => { - await expect(admin.accountsHideFlexTab()).toBeVisible(); - await expect(admin.accountsHideFlexTab().locator('input')).not.toBeChecked(); + await expect(admin.accountsHideFlexTab).toBeVisible(); + await expect(admin.accountsHideFlexTab.locator('input')).not.toBeChecked(); }); test('expect show display avatars and is checked', async () => { - await expect(admin.accountsDisplayAvatars().locator('input')).toBeVisible(); - await expect(admin.accountsDisplayAvatars().locator('input')).toBeChecked(); + await expect(admin.accountsDisplayAvatars.locator('input')).toBeVisible(); + await expect(admin.accountsDisplayAvatars.locator('input')).toBeChecked(); }); test('expect show the enter key behavior field', async () => { - await expect(admin.accountsSendOnEnter()).toBeVisible(); + await expect(admin.accountsSendOnEnter).toBeVisible(); - await expect(admin.accountsSendOnEnter().locator('.rcx-select__item')).toHaveText('Normal mode (send with Enter)'); + await expect(admin.accountsSendOnEnter.locator('.rcx-select__item')).toHaveText('Normal mode (send with Enter)'); }); test('the view mode field value should be ""', async () => { - await expect(admin.accountsMessageViewMode()).toHaveText(''); + await expect(admin.accountsMessageViewMode).toHaveText(''); }); test('expect show the offline email notification field and field value to be all', async () => { - await expect(admin.accountsEmailNotificationMode()).toBeVisible(); + await expect(admin.accountsEmailNotificationMode).toBeVisible(); }); test('expect the offline email notification field value should be all', async () => { - await expect(admin.accountsEmailNotificationMode().locator('.rcx-select__item')).toHaveText('Every Mention/DM'); + await expect(admin.accountsEmailNotificationMode.locator('.rcx-select__item')).toHaveText('Every Mention/DM'); }); test('expect show the new room notification field', async () => { - await expect(admin.accountsNewRoomNotification()).toBeVisible(); + await expect(admin.accountsNewRoomNotification).toBeVisible(); }); test('expect the new room notification field value should be door', async () => { - await expect(admin.accountsNewRoomNotification().locator('.rcx-select__item')).toHaveText('Default'); + await expect(admin.accountsNewRoomNotification.locator('.rcx-select__item')).toHaveText('Default'); }); test('expect show the new message notification field', async () => { - await expect(admin.accountsNewMessageNotification()).toBeVisible(); + await expect(admin.accountsNewMessageNotification).toBeVisible(); }); test('expect the new message notification field value should be chime', async () => { - await expect(admin.accountsNewMessageNotification().locator('.rcx-select__item')).toHaveText('Default'); + await expect(admin.accountsNewMessageNotification.locator('.rcx-select__item')).toHaveText('Default'); }); test('expect show the notification sound volume field', async () => { - await expect(admin.accountsNotificationsSoundVolume()).toBeVisible(); + await expect(admin.accountsNotificationsSoundVolume).toBeVisible(); }); test('the notification sound volume field value should be 100', async () => { - await expect(admin.accountsNotificationsSoundVolume()).toHaveValue('100'); + await expect(admin.accountsNotificationsSoundVolume).toHaveValue('100'); }); }); }); diff --git a/apps/meteor/tests/e2e/12-settings.spec.ts b/apps/meteor/tests/e2e/12-settings.spec.ts index d0893272dc61..4acbbcffcf0d 100644 --- a/apps/meteor/tests/e2e/12-settings.spec.ts +++ b/apps/meteor/tests/e2e/12-settings.spec.ts @@ -3,11 +3,7 @@ import { v4 as uuid } from 'uuid'; import { BASE_API_URL } from './utils/mocks/urlMock'; import { adminLogin, validUserInserted, registerUser } from './utils/mocks/userAndPasswordMock'; -import LoginPage from './utils/pageobjects/LoginPage'; -import MainContent from './utils/pageobjects/MainContent'; -import SideNav from './utils/pageobjects/SideNav'; -import Administration from './utils/pageobjects/Administration'; -import PreferencesMainContent from './utils/pageobjects/PreferencesMainContent'; +import { LoginPage, MainContent, SideNav, Administration, PreferencesMainContent } from './pageobjects'; const apiSessionHeaders = { 'X-Auth-Token': '', 'X-User-Id': '' }; @@ -29,7 +25,7 @@ test.describe.skip('[Settings]', async () => { await loginPage.goto('/'); await loginPage.login(validUserInserted); - await sideNav.general().click(); + await sideNav.general.click(); }); test.beforeAll(async ({ request }) => { @@ -140,7 +136,7 @@ test.describe.skip('[Settings]', async () => { test('(UI) expect option(upload audio) not be visible', async () => { await mainContent.doReload(); - expect(await mainContent.recordBtn().isVisible()).toBeFalsy(); + expect(await mainContent.recordBtn.isVisible()).toBeFalsy(); }); test('(API) expect enable audio files', async ({ request }) => { @@ -157,7 +153,7 @@ test.describe.skip('[Settings]', async () => { test('(UI) expect option(upload audio) be visible', async () => { await mainContent.doReload(); - expect(await mainContent.recordBtn().isVisible()).toBeTruthy(); + expect(await mainContent.recordBtn.isVisible()).toBeTruthy(); }); }); @@ -341,13 +337,13 @@ test.describe.skip('[Settings]', async () => { }); test.skip('(UI) expect options(update profile) be disabled', async () => { - await sideNav.sidebarUserMenu().click(); - await sideNav.account().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.account.click(); - expect(userPreferences.avatarFileInput().isDisabled()).toBeTruthy(); - expect(userPreferences.emailTextInput().isDisabled()).toBeTruthy(); - expect(userPreferences.realNameTextInput().isDisabled()).toBeTruthy(); - expect(userPreferences.userNameTextInput().isDisabled()).toBeTruthy(); + expect(userPreferences.avatarFileInput.isDisabled()).toBeTruthy(); + expect(userPreferences.emailTextInput.isDisabled()).toBeTruthy(); + expect(userPreferences.realNameTextInput.isDisabled()).toBeTruthy(); + expect(userPreferences.userNameTextInput.isDisabled()).toBeTruthy(); }); test('(API) expect enable profile change', async ({ request }) => { @@ -375,10 +371,10 @@ test.describe.skip('[Settings]', async () => { }); test.skip('(UI) expect option(update avatar) be disabled', async () => { - await sideNav.sidebarUserMenu().click(); - await sideNav.account().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.account.click(); - expect(userPreferences.avatarFileInput().isDisabled()).toBeTruthy(); + expect(userPreferences.avatarFileInput.isDisabled()).toBeTruthy(); }); test('(API) expect enable avatar change', async ({ request }) => { @@ -412,7 +408,7 @@ test.describe.skip('[Settings (admin)]', async () => { await loginPage.goto('/'); await loginPage.login(adminLogin); - await sideNav.general().click(); + await sideNav.general.click(); }); test.beforeAll(async ({ request }) => { @@ -478,22 +474,22 @@ test.describe.skip('[Settings (admin)]', async () => { test.describe('(UI) expect activate/deactivate flow as admin', () => { test('expect open /users as admin', async () => { await admin.goto('/admin'); - await admin.usersLink().click(); + await admin.usersLink.click(); }); test('expect find registered user', async () => { - await admin.usersFilter().type(registerUser.email, { delay: 200 }); + await admin.usersFilter.type(registerUser.email, { delay: 200 }); await admin.userInTable(registerUser.email).click(); }); test('expect activate registered user', async () => { - await admin.userInfoActions().locator('button:nth-child(3)').click(); - await admin.getPage().locator('[value="changeActiveStatus"]').click(); + await admin.userInfoActions.locator('button:nth-child(3)').click(); + await admin.page.locator('[value="changeActiveStatus"]').click(); }); test('expect deactivate registered user', async () => { - await admin.userInfoActions().locator('button:nth-child(3)').click(); - await admin.getPage().locator('[value="changeActiveStatus"]').click(); + await admin.userInfoActions.locator('button:nth-child(3)').click(); + await admin.page.locator('[value="changeActiveStatus"]').click(); }); }); diff --git a/apps/meteor/tests/e2e/13-permissions.spec.ts b/apps/meteor/tests/e2e/13-permissions.spec.ts index e92e6cba316e..1e9a84499c81 100644 --- a/apps/meteor/tests/e2e/13-permissions.spec.ts +++ b/apps/meteor/tests/e2e/13-permissions.spec.ts @@ -1,7 +1,7 @@ import { Page, test, expect } from '@playwright/test'; import { v4 as uuid } from 'uuid'; -import { LoginPage, FlexTab, Administration, MainContent, SideNav } from './utils/pageobjects'; +import { LoginPage, FlexTab, Administration, MainContent, SideNav } from './pageobjects'; import { adminLogin, createRegisterUser } from './utils/mocks/userAndPasswordMock'; import { BACKSPACE } from './utils/mocks/keyboardKeyMock'; @@ -28,34 +28,34 @@ test.describe('[Permissions]', () => { await page.goto('/'); await loginPage.login(adminLogin); - await sideNav.sidebarUserMenu().click(); - await sideNav.admin().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.admin.click(); await sideNav.users.click(); }); test('expect create a user via admin view', async () => { - await flexTab.usersAddUserTab().click(); - await flexTab.usersAddUserName().type(userToBeCreated.name); - await flexTab.usersAddUserUsername().type(userToBeCreated.username ?? ''); - await flexTab.usersAddUserEmail().type(userToBeCreated.email); - await flexTab.usersAddUserVerifiedCheckbox().click(); - await flexTab.usersAddUserPassword().type(userToBeCreated.password); + await flexTab.usersAddUserTab.click(); + await flexTab.usersAddUserName.type(userToBeCreated.name); + await flexTab.usersAddUserUsername.type(userToBeCreated.username ?? ''); + await flexTab.usersAddUserEmail.type(userToBeCreated.email); + await flexTab.usersAddUserVerifiedCheckbox.click(); + await flexTab.usersAddUserPassword.type(userToBeCreated.password); await flexTab.doAddRole('user'); - await flexTab.usersButtonSave().click(); + await flexTab.usersButtonSave.click(); }); test('expect user be show on list', async () => { - await admin.usersFilter().type(userToBeCreated.email, { delay: 200 }); + await admin.usersFilter.type(userToBeCreated.email, { delay: 200 }); await expect(admin.userInTable(userToBeCreated.email)).toBeVisible(); }); test.describe('disable "userToBeCreated" permissions', () => { test('expect open permissions table', async () => { - await admin.permissionsLink().click(); + await admin.permissionsLink.click(); }); test('expect remove "mention all" permission from user', async () => { - await admin.inputPermissionsSearch().type('all'); + await admin.inputPermissionsSearch.type('all'); if (await admin.getCheckboxPermission('Mention All').locator('input').isChecked()) { await admin.getCheckboxPermission('Mention All').click(); @@ -63,9 +63,9 @@ test.describe('[Permissions]', () => { }); test('expect remove "delete message" permission from user', async () => { - await admin.inputPermissionsSearch().click({ clickCount: 3 }); + await admin.inputPermissionsSearch.click({ clickCount: 3 }); await page.keyboard.press(BACKSPACE); - await admin.inputPermissionsSearch().type('delete'); + await admin.inputPermissionsSearch.type('delete'); if (await admin.getCheckboxPermission('Delete Own Message').locator('input').isChecked()) { await admin.getCheckboxPermission('Delete Own Message').click(); @@ -78,13 +78,13 @@ test.describe('[Permissions]', () => { await sideNav.doLogout(); await loginPage.goto('/'); await loginPage.login(userToBeCreated); - await sideNav.general().click(); + await sideNav.general.click(); }); test('expect not be abble to "mention all"', async () => { await mainContent.sendMessage('@all any_message'); - await expect(mainContent.lastMessage()).toContainText('not allowed'); + await expect(mainContent.lastMessage).toContainText('not allowed'); }); test('expect not be able to "delete own message"', async () => { diff --git a/apps/meteor/tests/e2e/14-setting-permissions.spec.ts b/apps/meteor/tests/e2e/14-setting-permissions.spec.ts index 94c621b6a00a..f4cd066ea0de 100644 --- a/apps/meteor/tests/e2e/14-setting-permissions.spec.ts +++ b/apps/meteor/tests/e2e/14-setting-permissions.spec.ts @@ -1,10 +1,8 @@ import { test, expect } from '@playwright/test'; import faker from '@faker-js/faker'; -import LoginPage from './utils/pageobjects/LoginPage'; import { adminLogin, validUserInserted } from './utils/mocks/userAndPasswordMock'; -import Administration from './utils/pageobjects/Administration'; -import SideNav from './utils/pageobjects/SideNav'; +import { SideNav, Administration, LoginPage } from './pageobjects'; test.describe('[Rocket.Chat Settings based permissions]', () => { let admin: Administration; @@ -23,9 +21,9 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { test.beforeAll(async () => { await loginPage.goto('/'); await loginPage.login(adminLogin); - await sideNav.sidebarUserMenu().click(); - await sideNav.admin().click(); - await admin.permissionsLink().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.admin.click(); + await admin.permissionsLink.click(); }); test.afterAll(async () => { @@ -34,23 +32,23 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { }); test('Set permission for user to manage settings', async () => { - await admin.rolesSettingsFindInput().type('settings'); - await admin.getPage().locator('table tbody tr:first-child td:nth-child(1) >> text="Change some settings"').waitFor(); - const isOptionChecked = await admin.getPage().isChecked('table tbody tr:first-child td:nth-child(6) label input'); + await admin.rolesSettingsFindInput.type('settings'); + await admin.page.locator('table tbody tr:first-child td:nth-child(1) >> text="Change some settings"').waitFor(); + const isOptionChecked = await admin.page.isChecked('table tbody tr:first-child td:nth-child(6) label input'); if (!isOptionChecked) { - await admin.getPage().click('table tbody tr:first-child td:nth-child(6) label'); + await admin.page.click('table tbody tr:first-child td:nth-child(6) label'); } }); test('Set Permission for user to change title page title', async () => { - await admin.rolesSettingsTab().click(); - await admin.rolesSettingsFindInput().fill('Layout'); - await admin.getPage().locator('table tbody tr:first-child td:nth-child(1) >> text="Layout"').waitFor(); - const isOptionChecked = await admin.getPage().isChecked('table tbody tr:first-child td:nth-child(6) label input'); - const changeHomeTitleSelected = await admin.getPage().isChecked('table tbody tr:nth-child(3) td:nth-child(6) label input'); + await admin.rolesSettingsTab.click(); + await admin.rolesSettingsFindInput.fill('Layout'); + await admin.page.locator('table tbody tr:first-child td:nth-child(1) >> text="Layout"').waitFor(); + const isOptionChecked = await admin.page.isChecked('table tbody tr:first-child td:nth-child(6) label input'); + const changeHomeTitleSelected = await admin.page.isChecked('table tbody tr:nth-child(3) td:nth-child(6) label input'); if (!isOptionChecked && !changeHomeTitleSelected) { - await admin.getPage().click('table tbody tr:first-child td:nth-child(6) label'); - await admin.getPage().click('table tbody tr:nth-child(3) td:nth-child(6) label'); + await admin.page.click('table tbody tr:first-child td:nth-child(6) label'); + await admin.page.click('table tbody tr:nth-child(3) td:nth-child(6) label'); } }); }); @@ -59,10 +57,10 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { test.beforeAll(async () => { await loginPage.goto('/'); await loginPage.login(validUserInserted); - await sideNav.sidebarUserMenu().click(); - await sideNav.admin().click(); - await admin.settingsLink().click(); - await admin.layoutSettingsButton().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.admin.click(); + await admin.settingsLink.click(); + await admin.layoutSettingsButton.click(); }); test.afterAll(async () => { await loginPage.goto('/home'); @@ -70,8 +68,8 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { }); test('expect new permissions is enabled for user', async () => { - await admin.homeTitleInput().fill(newHomeTitle); - await admin.buttonSave().click(); + await admin.homeTitleInput.fill(newHomeTitle); + await admin.buttonSave.click(); }); }); @@ -79,11 +77,11 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { test.beforeAll(async () => { await loginPage.goto('/'); await loginPage.login(adminLogin); - await sideNav.sidebarUserMenu().click(); - await sideNav.admin().click(); - await admin.settingsLink().click(); - await admin.settingsSearch().type('Layout'); - await admin.layoutSettingsButton().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.admin.click(); + await admin.settingsLink.click(); + await admin.settingsSearch.type('Layout'); + await admin.layoutSettingsButton.click(); }); test.afterAll(async () => { @@ -92,25 +90,25 @@ test.describe('[Rocket.Chat Settings based permissions]', () => { }); test('New settings value visible for admin as well', async () => { - await admin.getPage().locator('[data-qa-section="Content"]').click(); - await admin.homeTitleInput().waitFor(); - const text = await admin.homeTitleInput().inputValue(); - await admin.generalHomeTitleReset().click(); - await admin.buttonSave().click(); + await admin.page.locator('[data-qa-section="Content"]').click(); + await admin.homeTitleInput.waitFor(); + const text = await admin.homeTitleInput.inputValue(); + await admin.generalHomeTitleReset.click(); + await admin.buttonSave.click(); expect(text).toEqual(newHomeTitle); }); test('Clear all user permissions', async () => { - await admin.permissionsLink().click(); - await admin.rolesSettingsFindInput().type('settings'); - await admin.getPage().locator('table tbody tr:first-child td:nth-child(1) >> text="Change some settings"').waitFor(); - await admin.getPage().click('table tbody tr:first-child td:nth-child(6) label'); + await admin.permissionsLink.click(); + await admin.rolesSettingsFindInput.type('settings'); + await admin.page.locator('table tbody tr:first-child td:nth-child(1) >> text="Change some settings"').waitFor(); + await admin.page.click('table tbody tr:first-child td:nth-child(6) label'); - await admin.rolesSettingsTab().click(); - await admin.rolesSettingsFindInput().fill('Layout'); - await admin.getPage().locator('table tbody tr:first-child td:nth-child(1) >> text="Layout"').waitFor(); - await admin.getPage().click('table tbody tr td:nth-child(6) label'); - await admin.getPage().click('table tbody tr:nth-child(3) td:nth-child(6) label'); + await admin.rolesSettingsTab.click(); + await admin.rolesSettingsFindInput.fill('Layout'); + await admin.page.locator('table tbody tr:first-child td:nth-child(1) >> text="Layout"').waitFor(); + await admin.page.click('table tbody tr td:nth-child(6) label'); + await admin.page.click('table tbody tr:nth-child(3) td:nth-child(6) label'); }); }); }); diff --git a/apps/meteor/tests/e2e/15-message-popup.spec.ts b/apps/meteor/tests/e2e/15-message-popup.spec.ts index 2752ddec723e..b7a1e6b33593 100644 --- a/apps/meteor/tests/e2e/15-message-popup.spec.ts +++ b/apps/meteor/tests/e2e/15-message-popup.spec.ts @@ -2,9 +2,7 @@ import { Page, test, expect } from '@playwright/test'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; import { userMock } from './utils/mocks/userMock'; -import LoginPage from './utils/pageobjects/LoginPage'; -import MainContent from './utils/pageobjects/MainContent'; -import SideNav from './utils/pageobjects/SideNav'; +import { LoginPage, MainContent, SideNav } from './pageobjects'; test.describe('[Message Popup]', () => { let page: Page; @@ -28,27 +26,27 @@ test.describe('[Message Popup]', () => { test.describe('User mentions', () => { test('expect show message popup', async () => { await mainContent.setTextToInput('@'); - expect(await mainContent.messagePopUp().isVisible()).toBeTruthy(); + expect(await mainContent.messagePopUp.isVisible()).toBeTruthy(); }); test('expect popup title to be people', async () => { await mainContent.setTextToInput('@'); - expect(await mainContent.messagePopUpTitle().locator('text=People').isVisible()).toBeTruthy(); + expect(await mainContent.messagePopUpTitle.locator('text=People').isVisible()).toBeTruthy(); }); test('expect show "userMock.username" in options', async () => { await mainContent.setTextToInput('@'); - expect(await mainContent.messagePopUpItems().locator(`text=${userMock.username}`).isVisible()).toBeTruthy(); + expect(await mainContent.messagePopUpItems.locator(`text=${userMock.username}`).isVisible()).toBeTruthy(); }); test('expect show "all" option', async () => { await mainContent.setTextToInput('@'); - expect(await mainContent.messagePopUpItems().locator('text=all').isVisible()).toBeTruthy(); + expect(await mainContent.messagePopUpItems.locator('text=all').isVisible()).toBeTruthy(); }); test('expect show "here" option', async () => { await mainContent.setTextToInput('@'); - expect(await mainContent.messagePopUpItems().locator('text=here').isVisible()).toBeTruthy(); + expect(await mainContent.messagePopUpItems.locator('text=here').isVisible()).toBeTruthy(); }); }); }); diff --git a/apps/meteor/tests/e2e/16-discussion.spec.ts b/apps/meteor/tests/e2e/16-discussion.spec.ts index 2f7f7698c70c..50721afe9159 100644 --- a/apps/meteor/tests/e2e/16-discussion.spec.ts +++ b/apps/meteor/tests/e2e/16-discussion.spec.ts @@ -2,10 +2,7 @@ import { test, Page } from '@playwright/test'; import { faker } from '@faker-js/faker'; import { v4 as uuid } from 'uuid'; -import Discussion from './utils/pageobjects/Discussion'; -import LoginPage from './utils/pageobjects/LoginPage'; -import SideNav from './utils/pageobjects/SideNav'; -import MainContent from './utils/pageobjects/MainContent'; +import { MainContent, Discussion, LoginPage, SideNav } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; test.describe('[Discussion]', () => { @@ -34,7 +31,7 @@ test.describe('[Discussion]', () => { test('expect discussion is created', async () => { discussionName = faker.animal.type(); message = faker.animal.type(); - await sideNav.newChannelBtnToolbar().click(); + await sideNav.newChannelBtnToolbar.click(); await discussion.createDiscussion('public channel', discussionName, message); }); }); @@ -47,7 +44,7 @@ test.describe('[Discussion]', () => { }); test('expect show a dialog for starting a discussion', async () => { - await mainContent.getPage().waitForLoadState('domcontentloaded', { timeout: 3000 }); + await mainContent.page.waitForLoadState('domcontentloaded', { timeout: 3000 }); await mainContent.openMessageActionMenu(); await discussion.createDiscussionInContext(message); }); diff --git a/apps/meteor/tests/e2e/omnichannel-agents.spec.ts b/apps/meteor/tests/e2e/omnichannel-agents.spec.ts index ccc689b73419..1765371b131b 100644 --- a/apps/meteor/tests/e2e/omnichannel-agents.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel-agents.spec.ts @@ -1,15 +1,15 @@ import { test, expect, Page } from '@playwright/test'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; -import LoginPage from './utils/pageobjects/LoginPage'; -import SideNav from './utils/pageobjects/SideNav'; -import Agents from './utils/pageobjects/Agents'; +import { LoginPage, SideNav, Agents, Global } from './pageobjects'; test.describe('[Agents]', () => { let loginPage: LoginPage; let page: Page; let sideNav: SideNav; let agents: Agents; + let global: Global; + test.beforeAll(async ({ browser }) => { page = await browser.newPage(); const rootPath = '/'; @@ -17,48 +17,49 @@ test.describe('[Agents]', () => { loginPage = new LoginPage(page); sideNav = new SideNav(page); agents = new Agents(page); + global = new Global(page); await loginPage.login(adminLogin); - await sideNav.sidebarUserMenu().click(); - await sideNav.omnichannel().click(); - await agents.agentsLink().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.omnichannel.click(); + await agents.agentsLink.click(); await agents.doAddAgent(); }); test('expect admin/manager is able to add an agent', async () => { - await expect(agents.agentAdded()).toBeVisible(); - await expect(agents.agentAdded()).toHaveText('Rocket.Cat'); + await expect(agents.agentAdded).toBeVisible(); + await expect(agents.agentAdded).toHaveText('Rocket.Cat'); }); test('expect open new agent info on tab', async () => { - await agents.agentAdded().click(); - await expect(agents.userInfoTab()).toBeVisible(); - await expect(agents.agentInfo()).toBeVisible(); + await agents.agentAdded.click(); + await expect(agents.userInfoTab).toBeVisible(); + await expect(agents.agentInfo).toBeVisible(); }); test('expect close agent info on tab', async () => { - await agents.btnClose().click(); - await expect(agents.userInfoTab()).not.toBeVisible(); - await expect(agents.agentInfo()).not.toBeVisible(); - await agents.agentAdded().click(); + await agents.btnClose.click(); + await expect(agents.userInfoTab).not.toBeVisible(); + await expect(agents.agentInfo).not.toBeVisible(); + await agents.agentAdded.click(); }); test.describe('[Render]', () => { test('expect show profile image', async () => { - await expect(agents.userAvatar()).toBeVisible(); + await expect(agents.userAvatar).toBeVisible(); }); test('expect show action buttons', async () => { - await expect(agents.btnClose()).toBeVisible(); - await expect(agents.btnEdit()).toBeVisible(); - await expect(agents.btnRemove()).toBeVisible(); + await expect(agents.btnClose).toBeVisible(); + await expect(agents.btnEdit).toBeVisible(); + await expect(agents.btnRemove).toBeVisible(); }); test('expect show livechat status', async () => { - await expect(agents.agentInfoUserInfoLabel()).toBeVisible(); + await expect(agents.agentInfoUserInfoLabel).toBeVisible(); }); }); test.describe('[Edit button]', async () => { test.describe('[Render]', async () => { test.beforeAll(async () => { - await agents.btnEdit().click(); + await agents.btnEdit.click(); }); test('expect show fields', async () => { await agents.getListOfExpectedInputs(); @@ -68,20 +69,20 @@ test.describe('[Agents]', () => { test.describe('[Action]', async () => { test('expect change user status', async () => { await agents.doChangeUserStatus('not-available'); - await expect(agents.agentListStatus()).toHaveText('Not Available'); + await expect(agents.agentListStatus).toHaveText('Not Available'); }); test.describe('[Modal Actions]', async () => { test.beforeEach(async () => { await agents.doRemoveAgent(); }); test('expect modal is not visible after cancel delete agent', async () => { - await agents.btnModalCancel().click(); - await expect(agents.modal()).not.toBeVisible(); + await global.btnModalCancel.click(); + await expect(global.modal).not.toBeVisible(); }); test('expect agent is removed from user info tab', async () => { - await agents.btnModalRemove().click(); - await expect(agents.modal()).not.toBeVisible(); - await expect(agents.agentAdded()).not.toBeVisible(); + await global.btnModalRemove.click(); + await expect(global.modal).not.toBeVisible(); + await expect(agents.agentAdded).not.toBeVisible(); }); }); @@ -90,16 +91,16 @@ test.describe('[Agents]', () => { await agents.doAddAgent(); }); test.beforeEach(async () => { - await agents.btnTableRemove().click(); + await agents.btnTableRemove.click(); }); test('expect modal is not visible after cancel delete agent', async () => { - await agents.btnModalCancel().click(); - await expect(agents.modal()).not.toBeVisible(); + await global.btnModalCancel.click(); + await expect(global.modal).not.toBeVisible(); }); test('expect agent is removed from agents table', async () => { - await agents.btnModalRemove().click(); - await expect(agents.modal()).not.toBeVisible(); - await expect(agents.agentAdded()).not.toBeVisible(); + await global.btnModalRemove.click(); + await expect(global.modal).not.toBeVisible(); + await expect(agents.agentAdded).not.toBeVisible(); }); }); }); diff --git a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts index 1237156c2efd..ba74234189fc 100644 --- a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts @@ -1,9 +1,6 @@ import { test, Page, expect } from '@playwright/test'; -import LoginPage from './utils/pageobjects/LoginPage'; -import Global from './utils/pageobjects/Global'; -import SideNav from './utils/pageobjects/SideNav'; -import Departments from './utils/pageobjects/Departments'; +import { Departments, SideNav, Global, LoginPage } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; test.describe('[Department]', () => { @@ -24,8 +21,8 @@ test.describe('[Department]', () => { global = new Global(page); await loginPage.login(adminLogin); - await sideNav.sidebarUserMenu().click(); - await sideNav.omnichannel().click(); + await sideNav.sidebarUserMenu.click(); + await sideNav.omnichannel.click(); }); test.describe('[Render]', async () => { diff --git a/apps/meteor/tests/e2e/pageobjects/Administration.ts b/apps/meteor/tests/e2e/pageobjects/Administration.ts new file mode 100644 index 000000000000..b0c4e45c5a59 --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/Administration.ts @@ -0,0 +1,365 @@ +import { Locator, expect } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class Administration extends BasePage { + get settingsSearch(): Locator { + return this.page.locator('input[type=search]'); + } + + get settingsLink(): Locator { + return this.page.locator('.flex-nav [href="/admin/settings"]'); + } + + get groupSettingsPageBack(): Locator { + return this.page.locator('button[title="Back"]'); + } + + get infoLink(): Locator { + return this.page.locator('//div[contains(text(),"Info")]'); + } + + get roomsLink(): Locator { + return this.page.locator('.flex-nav [href="/admin/rooms"]'); + } + + get usersLink(): Locator { + return this.page.locator('.flex-nav [href="/admin/users"]'); + } + + get accountSettingsButton(): Locator { + return this.page.locator('[data-qa-id="Accounts"] button'); + } + + get generalSettingsButton(): Locator { + return this.page.locator('[data-qa-id="General"] button'); + } + + get layoutSettingsButton(): Locator { + return this.page.locator('[data-qa-id="Layout"] button'); + } + + get permissionsLink(): Locator { + return this.page.locator('.flex-nav [href="/admin/permissions"]'); + } + + get infoDeployment(): Locator { + return this.page.locator('//div[text()="Deployment"]'); + } + + get infoLicense(): Locator { + return this.page.locator('//div[text()="License"]'); + } + + get infoUsage(): Locator { + return this.page.locator('//div[text()="Usage"]'); + } + + get infoFederation(): Locator { + return this.page.locator('//section[@data-qa="admin-info"]//div[text()="Federation"]'); + } + + get roomsSearchForm(): Locator { + return this.page.locator('input[placeholder ="Search Rooms"]'); + } + + public async verifyCheckBoxRendered(checkBoxes: string[]): Promise { + const expected = []; + for (const checkBox of checkBoxes) { + expected.push(expect(this.adminCheckBox(checkBox)).toBeVisible()); + } + await Promise.all(expected); + } + + get roomsGeneralChannel(): Locator { + return this.page.locator('//table//tbody//tr[1]//..//div//div//div//div[text()="general"]'); + } + + get notFoundChannelOrUser(): Locator { + return this.page.locator("//div[text()='No data found']"); + } + + get notFoundChannels(): Locator { + return this.page.locator("//div[text()='No results found']"); + } + + get usersFilter(): Locator { + return this.page.locator('input[placeholder="Search Users"]'); + } + + public userInTable(id: string): Locator { + return this.page.locator(`tr > td:has-text("${id}")`); + } + + get rolesSettingsFindInput(): Locator { + return this.page.locator('[data-qa="PermissionTable-PermissionsTableFilter"]'); + } + + get rolesSettingsTab(): Locator { + return this.page.locator('[data-qa="PermissionTable-Settings"]'); + } + + get homeTitleInput(): Locator { + return this.page.locator('[data-qa-setting-id="Layout_Home_Title"]'); + } + + get buttonSave(): Locator { + return this.page.locator('button.save'); + } + + get generalSectionIframeIntegration(): Locator { + return this.page.locator('[data-qa-section="Iframe_Integration"]'); + } + + get generalIframeSendTargetOrigin(): Locator { + return this.page.locator('[data-qa-setting-id="Iframe_Integration_send_target_origin"]'); + } + + get generalSectionNotifications(): Locator { + return this.page.locator('[data-qa-section="Notifications"]'); + } + + get generalSectionRestApi(): Locator { + return this.page.locator('[data-qa-section="REST API"]'); + } + + get generalSectionReporting(): Locator { + return this.page.locator('[data-qa-section="Reporting"]'); + } + + get generalSectionStreamCast(): Locator { + return this.page.locator('[data-qa-section="Stream_Cast"]'); + } + + get generalSectionUTF8(): Locator { + return this.page.locator('[data-qa-section="UTF8"]'); + } + + get generalSiteUrl(): Locator { + return this.page.locator('[data-qa-setting-id="Site_Url"]'); + } + + get generalSiteUrlReset(): Locator { + return this.page.locator('//label[@title="Site_Url"]//following-sibling::button'); + } + + get generalSiteName(): Locator { + return this.page.locator('[data-qa-setting-id="Site_Name"]'); + } + + get generalSiteNameReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="Site_Name"]'); + } + + get generalHomeTitleReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="Layout_Home_Title"]'); + } + + get generalLanguage(): Locator { + return this.page.locator('[data-qa-setting-id="Language"]'); + } + + get generalSelfSignedCerts(): Locator { + return this.page.locator('//label[@data-qa-setting-id="Allow_Invalid_SelfSigned_Certs"]//i'); + } + + get generalSelfSignedCertsReset(): Locator { + return this.page.locator('//button[@data-qa-reset-setting-id="Allow_Invalid_SelfSigned_Certs"]'); + } + + get generalFavoriteRoom(): Locator { + return this.page.locator('[data-qa-setting-id="Favorite_Rooms"]'); + } + + get generalFavoriteRoomReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="Favorite_Rooms"]'); + } + + get generalCdnPrefix(): Locator { + return this.page.locator('[data-qa-setting-id="CDN_PREFIX"]'); + } + + get generalCdnPrefixReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="CDN_PREFIX"]'); + } + + get generalForceSSL(): Locator { + return this.page.locator('[data-qa-setting-id="Force_SSL"]'); + } + + get generalForceSSLReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="Force_SSL"]'); + } + + get generalGoogleTagId(): Locator { + return this.page.locator('[data-qa-setting-id="GoogleTagManager_id"]'); + } + + get generalGoogleTagIdReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="GoogleTagManager_id"]'); + } + + get generalBugsnagKey(): Locator { + return this.page.locator('[data-qa-setting-id="Bugsnag_api_key"]'); + } + + get generalBugsnagKeyReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="Bugsnag_api_key"]'); + } + + get robotsFileContents(): Locator { + return this.page.locator('#Robot_Instructions_File_Content'); + } + + get robotsFileContentsReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="Robot_Instructions_File_Content"]'); + } + + get defaultReferrerPolicy(): Locator { + return this.page.locator('//div[@data-qa-setting-id="Default_Referrer_Policy"]'); + } + + get defaultReferrerPolicyReset(): Locator { + return this.page.locator('[data-qa-reset-setting-id="Default_Referrer_Policy"]'); + } + + get defaultReferrerPolicyOptions(): Locator { + return this.page.locator('.rcx-options .rcx-option:first-child'); + } + + get generalIframeSend(): Locator { + return this.page.locator('[data-qa-setting-id="Iframe_Integration_send_enable"]'); + } + + get generalIframeReceive(): Locator { + return this.page.locator('[data-qa-setting-id="Iframe_Integration_receive_enable"]'); + } + + get generalIframeReceiveOrigin(): Locator { + return this.page.locator('[data-qa-setting-id="Iframe_Integration_receive_origin"]'); + } + + get generalNotificationsMaxRoomMembers(): Locator { + return this.page.locator('[data-qa-setting-id="Notifications_Max_Room_Members"]'); + } + + get generalRestApiUserLimit(): Locator { + return this.page.locator('[data-qa-setting-id="API_User_Limit"]'); + } + + get generalReporting(): Locator { + return this.page.locator('[data-qa-setting-id="Statistics_reporting"]'); + } + + get generalStreamCastAddress(): Locator { + return this.page.locator('[data-qa-setting-id="Stream_Cast_Address"]'); + } + + get generalUTF8UsernamesRegex(): Locator { + return this.page.locator('[data-qa-setting-id="UTF8_User_Names_Validation"]'); + } + + get generalUTF8ChannelsRegex(): Locator { + return this.page.locator('[data-qa-setting-id="UTF8_Channel_Names_Validation"]'); + } + + get generalUTF8NamesSlug(): Locator { + return this.page.locator('[data-qa-setting-id="UTF8_Names_Slugify"]'); + } + + get accountsSectionDefaultUserPreferences(): Locator { + return this.page.locator('[data-qa-section="Accounts_Default_User_Preferences"]'); + } + + get accountsEnableAutoAway(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_enableAutoAway"]'); + } + + get accountsIdleTimeLimit(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_idleTimeLimit"]'); + } + + get accountsDesktopNotifications(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_desktopNotifications"]'); + } + + get accountsMobileNotifications(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_pushNotifications"]'); + } + + get accountsUnreadAlert(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_unreadAlert"]'); + } + + get accountsConvertAsciiEmoji(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_convertAsciiEmoji"]'); + } + + get accountsAutoImageLoad(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_autoImageLoad"]'); + } + + get accountsSaveMobileBandwidth(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_saveMobileBandwidth"]'); + } + + get accountsCollapseMediaByDefault(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_collapseMediaByDefault"]'); + } + + get accountsHideUsernames(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_hideUsernames"]'); + } + + get accountsHideRoles(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_hideRoles"]'); + } + + get accountsHideFlexTab(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_hideFlexTab"]'); + } + + get accountsDisplayAvatars(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_displayAvatars"]'); + } + + get accountsSendOnEnter(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_sendOnEnter"]'); + } + + get accountsMessageViewMode(): Locator { + return this.page.locator('//div[@data-qa-setting-id="Accounts_Default_User_Preferences_messageViewMode"]//div//span'); + } + + get accountsEmailNotificationMode(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_emailNotificationMode"]'); + } + + get accountsNewRoomNotification(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_newRoomNotification"]'); + } + + get accountsNewMessageNotification(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_newMessageNotification"]'); + } + + get accountsNotificationsSoundVolume(): Locator { + return this.page.locator('[data-qa-setting-id="Accounts_Default_User_Preferences_notificationsSoundVolume"]'); + } + + public adminCheckBox(checkBox: string): Locator { + return this.page.locator(`//label[text()="${checkBox}"]/preceding-sibling::label/i`); + } + + get inputPermissionsSearch(): Locator { + return this.page.locator('.main-content input[placeholder="Search"]'); + } + + public getCheckboxPermission(label: string, column = 6): Locator { + return this.page.locator(`tr td:has-text("${label}") ~ td:nth-child(${column})`).locator('label').first(); + } + + get userInfoActions(): Locator { + return this.page.locator('[data-qa-id="UserInfoActions"]'); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/Agents.ts b/apps/meteor/tests/e2e/pageobjects/Agents.ts new file mode 100644 index 000000000000..784f5c9cee7e --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/Agents.ts @@ -0,0 +1,110 @@ +import { Locator, expect } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class Agents extends BasePage { + get agentsLink(): Locator { + return this.page.locator('a[href="omnichannel/agents"]'); + } + + get textAgentsTitle(): Locator { + return this.page.locator('h2 >> text="Agents"'); + } + + get inputAgentsUserName(): Locator { + return this.page.locator('input').first(); + } + + get userOption(): Locator { + return this.page.locator('.rcx-option >> text="Rocket.Cat"'); + } + + get btnAddAgents(): Locator { + return this.page.locator('button.rcx-button--primary.rcx-button >> text="Add"'); + } + + get agentAdded(): Locator { + return this.page.locator('[data-qa="GenericTableAgentInfoBody"] .rcx-table__row--action .rcx-table__cell:first-child'); + } + + get agentListStatus(): Locator { + return this.page.locator('[data-qa="GenericTableAgentInfoBody"] .rcx-table__row--action .rcx-table__cell:nth-child(4)'); + } + + get userInfoTab(): Locator { + return this.page.locator('h3 div'); + } + + get agentInfo(): Locator { + return this.page.locator('[data-qa="AgentInfoUserInfoUserName"]'); + } + + get agentInfoUserInfoLabel(): Locator { + return this.page.locator('[data-qa="AgentInfoUserInfoLabel"]'); + } + + get btnClose(): Locator { + return this.page.locator('[data-qa="VerticalBarActionClose"]'); + } + + get userAvatar(): Locator { + return this.page.locator('[data-qa="AgentUserInfoAvatar"]'); + } + + get btnEdit(): Locator { + return this.page.locator('[data-qa="AgentInfoAction-Edit"]'); + } + + get btnRemove(): Locator { + return this.page.locator('[data-qa="AgentInfoAction-Remove"]'); + } + + public availabilityOption(availability: string): Locator { + return this.page.locator(`div.rcx-options[role="listbox"] div.rcx-box ol[role="listbox"] li[value="${availability}"]`); + } + + get btnTableRemove(): Locator { + return this.page.locator( + '[data-qa="GenericTableAgentInfoBody"] .rcx-table__row--action .rcx-table__cell:nth-child(5) [title="Remove"]', + ); + } + + get agentStatus(): Locator { + return this.page.locator('[data-qa="AgentEditTextInput-Status"]'); + } + + get btnAgentSave(): Locator { + return this.page.locator('[data-qa="AgentEditButtonSave"]'); + } + + public getAgentInputs(id: string): Locator { + return this.page.locator(`[data-qa="AgentEditTextInput-${id}"]`); + } + + public async doAddAgent(): Promise { + await this.textAgentsTitle.waitFor(); + await this.inputAgentsUserName.type('Rocket.Cat', { delay: 50 }); + + await this.userOption.click(); + await this.btnAddAgents.click(); + } + + public async getListOfExpectedInputs(): Promise { + const inputs = ['Name', 'Username', 'Email', 'Departaments', 'Status'].map((id) => this.getAgentInputs(id)); + await Promise.all(inputs.map((input) => expect(input).toBeVisible())); + await this.btnClose.click(); + } + + public async doChangeUserStatus(availability: string): Promise { + await this.agentAdded.click(); + await this.btnEdit.click(); + await this.agentStatus.click(); + await this.availabilityOption(availability).click(); + await this.btnAgentSave.click(); + } + + public async doRemoveAgent(): Promise { + await this.agentAdded.click(); + await this.btnRemove.click(); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/BasePage.ts b/apps/meteor/tests/e2e/pageobjects/BasePage.ts new file mode 100644 index 000000000000..424e88a43e32 --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/BasePage.ts @@ -0,0 +1,21 @@ +import { Page } from '@playwright/test'; + +export class BasePage { + public page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto(path: string): Promise { + await this.page.goto(path); + } + + async waitForSelector(selector: string): Promise { + await this.page.waitForSelector(selector); + } + + async keyboardPress(key: string): Promise { + await this.page.keyboard.press(key); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/ChannelCreation.ts b/apps/meteor/tests/e2e/pageobjects/ChannelCreation.ts new file mode 100644 index 000000000000..b853bda1dd95 --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/ChannelCreation.ts @@ -0,0 +1,75 @@ +import { Locator, expect } from '@playwright/test'; + +import { BasePage } from './BasePage'; +import { ENTER } from '../utils/mocks/keyboardKeyMock'; + +export class ChannelCreation extends BasePage { + private get buttonCreate(): Locator { + return this.page.locator('[data-qa="sidebar-create"]'); + } + + private get inputChannelName(): Locator { + return this.page.locator('[placeholder="Channel Name"]'); + } + + private get inputChannelDescription(): Locator { + return this.page.locator('[placeholder="What is this channel about?"]'); + } + + private get buttonCreateChannel(): Locator { + return this.page.locator('//ul[@class="rc-popover__list"]//li[@class="rcx-option"][1]'); + } + + private get channelName(): Locator { + return this.page.locator('//header//div//div//div//div[2]'); + } + + private get buttonConfirmCreation(): Locator { + return this.page.locator('//button[contains(text(), "Create" )]'); + } + + private get privateChannel(): Locator { + return this.page.locator('//label[contains(text(),"Private")]/../following-sibling::label/i'); + } + + private get searchChannel(): Locator { + return this.page.locator('[data-qa="sidebar-search"]'); + } + + private get searchChannelInput(): Locator { + return this.page.locator('[data-qa="sidebar-search-input"]'); + } + + private get textArea(): Locator { + return this.page.locator('.rc-message-box__textarea'); + } + + private get lastMessage(): Locator { + return this.page.locator('.message:last-child .body'); + } + + public async createChannel(name: string, isPrivate: boolean): Promise { + await this.buttonCreate.click(); + await this.buttonCreateChannel.click(); + await this.inputChannelName.type(name); + await this.inputChannelDescription.type('any_description'); + if (!isPrivate) { + await this.privateChannel.click(); + } + await this.buttonConfirmCreation.click(); + + await expect(this.channelName).toHaveText(name); + } + + public async sendMessage(targetUser: string, message: string): Promise { + await this.searchChannel.click(); + await this.searchChannelInput.type(targetUser, { delay: 200 }); + await this.keyboardPress(ENTER); + + await this.textArea.type(message); + await this.keyboardPress(ENTER); + + await expect(this.lastMessage).toBeVisible(); + await expect(this.lastMessage).toHaveText(message); + } +} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/Departments.ts b/apps/meteor/tests/e2e/pageobjects/Departments.ts similarity index 60% rename from apps/meteor/tests/e2e/utils/pageobjects/Departments.ts rename to apps/meteor/tests/e2e/pageobjects/Departments.ts index 09b6af0c147c..21837bfb563a 100644 --- a/apps/meteor/tests/e2e/utils/pageobjects/Departments.ts +++ b/apps/meteor/tests/e2e/pageobjects/Departments.ts @@ -1,87 +1,87 @@ import { Locator, expect } from '@playwright/test'; -import BasePage from './BasePage'; +import { BasePage } from './BasePage'; -export default class Departments extends BasePage { +export class Departments extends BasePage { get departmentsLink(): Locator { - return this.getPage().locator('a[href="omnichannel/departments"]'); + return this.page.locator('a[href="omnichannel/departments"]'); } get btnNewDepartment(): Locator { - return this.getPage().locator('button.rcx-button >> text="New"'); + return this.page.locator('button.rcx-button >> text="New"'); } get btnSaveDepartment(): Locator { - return this.getPage().locator('button.rcx-button--primary.rcx-button >> text="Save"'); + return this.page.locator('button.rcx-button--primary.rcx-button >> text="Save"'); } get btnBack(): Locator { - return this.getPage().locator('button.rcx-button >> text="Back"'); + return this.page.locator('button.rcx-button >> text="Back"'); } get enabledToggle(): Locator { // temporary selector - return this.getPage().locator('[data-qa="DepartmentEditToggle-Enabled"] span label'); + return this.page.locator('[data-qa="DepartmentEditToggle-Enabled"] span label'); } get nameInput(): Locator { - return this.getPage().locator('[data-qa="DepartmentEditTextInput-Name"]'); + return this.page.locator('[data-qa="DepartmentEditTextInput-Name"]'); } get descriptionInput(): Locator { - return this.getPage().locator('[data-qa="DepartmentEditTextInput-Description"]'); + return this.page.locator('[data-qa="DepartmentEditTextInput-Description"]'); } get showOnRegistrationPage(): Locator { - return this.getPage().locator('[data-qa="DepartmentEditToggle-ShowOnRegistrationPage"] span label'); + return this.page.locator('[data-qa="DepartmentEditToggle-ShowOnRegistrationPage"] span label'); } get emailInput(): Locator { - return this.getPage().locator('[data-qa="DepartmentEditTextInput-Email"]'); + return this.page.locator('[data-qa="DepartmentEditTextInput-Email"]'); } get showOnOfflinePageToggle(): Locator { - return this.getPage().locator('[data-qa="DepartmentEditToggle-ShowOnOfflinePage"] span label'); + return this.page.locator('[data-qa="DepartmentEditToggle-ShowOnOfflinePage"] span label'); } get selectLiveChatDepartmentOfflineMessageToChannel(): Locator { - return this.getPage().locator('[data-qa="DepartmentSelect-LivechatDepartmentOfflineMessageToChannel"]'); + return this.page.locator('[data-qa="DepartmentSelect-LivechatDepartmentOfflineMessageToChannel"]'); } get requestTagBeforeClosingChatToggle(): Locator { - return this.getPage().locator('[data-qa="DiscussionToggle-RequestTagBeforeCLosingChat"] span label'); + return this.page.locator('[data-qa="DiscussionToggle-RequestTagBeforeCLosingChat"] span label'); } get selectAgentsTable(): Locator { - return this.getPage().locator('[data-qa="DepartmentSelect-AgentsTable"]'); + return this.page.locator('[data-qa="DepartmentSelect-AgentsTable"]'); } get btnAddAgent(): Locator { - return this.getPage().locator('button.rcx-button--primary.rcx-button >> text="Add"'); + return this.page.locator('button.rcx-button--primary.rcx-button >> text="Add"'); } public virtuosoOptions(option: string): Locator { - return this.getPage().locator(`[data-test-id="virtuoso-scroller"] .rcx-option >> text="${option}"`); + return this.page.locator(`[data-test-id="virtuoso-scroller"] .rcx-option >> text="${option}"`); } get departmentAdded(): Locator { - return this.getPage().locator('table tr:first-child td:first-child '); + return this.page.locator('table tr:first-child td:first-child '); } get btnTableDeleteDepartment(): Locator { - return this.getPage().locator('table tr:first-child td:nth-child(6) button'); + return this.page.locator('table tr:first-child td:nth-child(6) button'); } get btnModalCancelDeleteDepartment(): Locator { - return this.getPage().locator('#modal-root .rcx-modal .rcx-modal__footer .rcx-button--ghost'); + return this.page.locator('#modal-root .rcx-modal .rcx-modal__footer .rcx-button--ghost'); } get btnModalDeleteDepartment(): Locator { - return this.getPage().locator('#modal-root .rcx-modal .rcx-modal__footer .rcx-button--primary-danger'); + return this.page.locator('#modal-root .rcx-modal .rcx-modal__footer .rcx-button--primary-danger'); } get modalDepartment(): Locator { - return this.getPage().locator('#modal-root'); + return this.page.locator('#modal-root'); } public async getAddScreen(): Promise { diff --git a/apps/meteor/tests/e2e/pageobjects/Discussion.ts b/apps/meteor/tests/e2e/pageobjects/Discussion.ts new file mode 100644 index 000000000000..859aea57c1e1 --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/Discussion.ts @@ -0,0 +1,54 @@ +import { Locator, expect } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class Discussion extends BasePage { + get startDiscussionContextItem(): Locator { + return this.page.locator('[data-qa-id="start-discussion"][data-qa-type="message-action"]'); + } + + get createDiscussionBtn(): Locator { + return this.page.locator('.rcx-option__content >> text="Discussion"'); + } + + get channelName(): Locator { + return this.page.locator('.rcx-input-box--undecorated.rcx-input-box').first(); + } + + get discussionName(): Locator { + return this.page.locator('[placeholder="A meaningful name for the discussion room"]'); + } + + get discussionMessage(): Locator { + return this.page.locator('textarea.rcx-input-box'); + } + + get buttonCreateDiscussion(): Locator { + return this.page.locator('button.rcx-button--primary.rcx-button >> text="Create"'); + } + + public discussionCreated(discussionName: string): Locator { + return this.page.locator(`[data-qa="sidebar-item-title"] >> text='${discussionName}'`); + } + + async createDiscussion(channelName: string, discussionName: string, message: string): Promise { + await this.createDiscussionBtn.click(); + await this.channelName.type(channelName); + await this.page.keyboard.press('Enter'); + + await this.discussionName.type(discussionName); + await this.discussionMessage.type(message); + + await this.buttonCreateDiscussion.click(); + + await expect(this.discussionCreated(discussionName)).toBeVisible(); + } + + async createDiscussionInContext(message: string): Promise { + await this.startDiscussionContextItem.waitFor(); + await this.page.pause(); + await this.startDiscussionContextItem.click(); + await this.buttonCreateDiscussion.click(); + await expect(this.discussionCreated(message)).toBeVisible(); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/FlexTab.ts b/apps/meteor/tests/e2e/pageobjects/FlexTab.ts new file mode 100644 index 000000000000..284ce14fb20a --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/FlexTab.ts @@ -0,0 +1,410 @@ +import { expect, Locator, Page } from '@playwright/test'; + +import { BasePage } from './BasePage'; +import { Global } from './Global'; + +export class FlexTab extends BasePage { + private global: Global; + + constructor(page: Page) { + super(page); + this.global = new Global(page); + } + + get mainSideBar(): Locator { + return this.page.locator('//main//aside'); + } + + get mainSideBarBack(): Locator { + return this.page.locator('(//main//aside/h3//button)[1]'); + } + + get mainSideBarClose(): Locator { + return this.page.locator('//main//aside/h3//i[contains(@class, "rcx-icon--name-cross")]/..'); + } + + get headerMoreActions(): Locator { + return this.page.locator('//main/header//*[contains(@class, "rcx-icon--name-kebab")]/..'); + } + + get messageInput(): Locator { + return this.page.locator('.rcx-vertical-bar .js-input-message'); + } + + get channelTab(): Locator { + return this.page.locator('(//main//*[contains(@class, "rcx-icon--name-info-circled")])[1]/..'); + } + + get channelSettings(): Locator { + return this.page.locator( + '//aside/h3/div/i[contains(@class,"rcx-icon--name-info-circled") and contains(@class,"rcx-icon--name-info-circled")]', + ); + } + + get editNameBtn(): Locator { + return this.page.locator('//aside//button[contains(text(), "Edit")]'); + } + + get editUnreadAlertBtn(): Locator { + return this.page.locator('[data-edit="unreadAlert"]'); + } + + get editNameTextInput(): Locator { + return this.page.locator('//aside//label[contains(text(), "Name")]/..//input'); + } + + get editTopicTextInput(): Locator { + return this.page.locator('//main//aside//label[contains(text(), "Topic")]/..//textarea'); + } + + get editAnnouncementTextInput(): Locator { + return this.page.locator('//main//aside//label[contains(text(), "Announcement")]/..//textarea'); + } + + get editDescriptionTextInput(): Locator { + return this.page.locator('//main//aside//label[contains(text(), "Description")]/..//textarea'); + } + + get editNameSave(): Locator { + return this.page.locator('//aside//button[contains(text(), "Save")]'); + } + + get membersTab(): Locator { + return this.page.locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-members'); + } + + get membersTabContent(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-members'); + } + + get setOwnerBtn(): Locator { + return this.page.locator('//main//aside//button[contains(text(), "Set as owner")]'); + } + + get setModeratorBtn(): Locator { + return this.page.locator('[value="changeModerator"]'); + } + + get muteUserBtn(): Locator { + return this.page.locator('[value="muteUser"]'); + } + + get avatarImage(): Locator { + return this.page.locator('(//aside[contains(@class, "rcx-vertical-bar")]//*[contains(@class, "avatar")])[1]'); + } + + get memberRealName(): Locator { + return this.page.locator('[data-qa="UserInfoUserName"]'); + } + + get searchTab(): Locator { + return this.page.locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-magnifier'); + } + + get searchTabContent(): Locator { + return this.page.locator('.rocket-search-result'); + } + + get messageSearchBar(): Locator { + return this.page.locator('#message-search'); + } + + get searchResult(): Locator { + return this.page.locator('.new-day'); + } + + get notificationsTab(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Notifications Preferences")]'); + } + + get notificationsSettings(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-bell'); + } + + get filesTab(): Locator { + return this.page.locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-clip'); + } + + get filesTabContent(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-icon--name-attachment'); + } + + get fileDownload(): Locator { + return this.page.locator('.uploaded-files-list ul:first-child .file-download'); + } + + get fileName(): Locator { + return this.page.locator('.uploaded-files-list ul:first-child .room-file-item'); + } + + get mentionsTab(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Mentions")]'); + } + + get mentionsTabContent(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-icon--name-at'); + } + + get starredTab(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Starred Messages")]'); + } + + get starredTabContent(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-icon--name-star'); + } + + get pinnedTab(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Pinned Messages")]'); + } + + get pinnedTabContent(): Locator { + return this.page.locator('aside > h3 > div > i.rcx-icon--name-pin'); + } + + get firstSetting(): Locator { + return this.page.locator('//aside//i[contains(@class, "rcx-icon--name-hashtag")]/../div'); + } + + public secondSetting(topic: string): Locator { + return this.page.locator(`//header//*[contains(text(), "${topic}")]`); + } + + get thirdSetting(): Locator { + return this.page.locator('[data-qa="AnnouncementAnnoucementComponent"] div:nth-child(1)'); + } + + get fourthSetting(): Locator { + return this.page.locator('//main//aside//div[contains(text(), "Description")]//following-sibling::div'); + } + + get usersAddUserTab(): Locator { + return this.page.locator('//button[text()="New"]'); + } + + get usersAddUserTabClose(): Locator { + return this.page.locator('//div[text()="Add User"]//button'); + } + + get usersButtonCancel(): Locator { + return this.page.locator('//button[text()="Cancel"]'); + } + + get usersButtonSave(): Locator { + return this.page.locator('//button[text()="Save"]'); + } + + get usersAddUserName(): Locator { + return this.page.locator('//label[text()="Name"]/following-sibling::span//input'); + } + + get usersAddUserUsername(): Locator { + return this.page.locator('//label[text()="Username"]/following-sibling::span//input'); + } + + get usersAddUserEmail(): Locator { + return this.page.locator('//label[text()="Email"]/following-sibling::span//input').first(); + } + + get usersAddUserRoleList(): Locator { + return this.page.locator('//label[text()="Roles"]/following-sibling::span//input'); + } + + get fileDescription(): Locator { + return this.page.locator( + '//li[@data-username="rocketchat.internal.admin.test"][last()]//div[@class="js-block-wrapper"]/following-sibling::div//div//p', + ); + } + + get usersAddUserPassword(): Locator { + return this.page.locator('//label[text()="Password"]/following-sibling::span//input'); + } + + get usersAddUserVerifiedCheckbox(): Locator { + return this.page.locator('//label[text()="Email"]/following-sibling::span//input/following-sibling::i'); + } + + get usersAddUserChangePasswordCheckbox(): Locator { + return this.page.locator('//div[text()="Require password change"]/following-sibling::label//input'); + } + + get usersAddUserDefaultChannelCheckbox(): Locator { + return this.page.locator('//div[text()="Join default channels"]/following-sibling::label//input'); + } + + get usersAddUserWelcomeEmailCheckbox(): Locator { + return this.page.locator('//div[text()="Send welcome email"]/following-sibling::label//input'); + } + + get usersAddUserRandomPassword(): Locator { + return this.page.locator('//div[text()="Set random password and send by email"]/following-sibling::label//input'); + } + + get closeThreadMessage(): Locator { + return this.page.locator('//html//body//div[1]//div//div[3]//div[1]//main//div//aside//div[2]//div//div//h3//div//div[2]//button[2]'); + } + + public getUserEl(username: string): Locator { + return this.page.locator(`[data-qa="MemberItem-${username}"]`); + } + + get addUserTable(): Locator { + return this.page.locator('//div[text()="Add User"]'); + } + + get addUserButton(): Locator { + return this.page.locator('//button[contains(text(), "Add")]'); + } + + get addUserButtonAfterChoose(): Locator { + return this.page.locator('//button[contains(text(), "Add users")]'); + } + + get chooseUserSearch(): Locator { + return this.page.locator('//label[contains(text(), "Choose users")]/..//input'); + } + + get chooseUserOptions(): Locator { + return this.page.locator('(//div[@role="option"]//ol/li)[1]'); + } + + get userMoreActions(): Locator { + return this.page.locator('[data-qa="UserUserInfo-menu"]'); + } + + public async setUserOwner(user: string): Promise { + await this.enterUserView(user); + await this.setOwnerBtn.waitFor(); + await this.setOwnerBtn.click(); + } + + public async setUserModerator(user: string): Promise { + await this.enterUserView(user); + await this.userMoreActions.click(); + await this.setModeratorBtn.waitFor(); + await this.setModeratorBtn.click(); + } + + public async muteUser(user: string): Promise { + await this.enterUserView(user); + await this.userMoreActions.click(); + await this.muteUserBtn.waitFor(); + await this.muteUserBtn.click(); + await this.global.confirmPopup(); + await this.mainSideBarBack.click(); + } + + public async enterUserView(user: string): Promise { + const userEl = this.getUserEl(user); + await userEl.waitFor(); + await userEl.click(); + } + + public async addPeopleToChannel(user: string): Promise { + await this.addUserButton.click(); + await this.chooseUserSearch.type(user); + await this.page.waitForTimeout(3000); + await this.chooseUserOptions.click(); + await this.addUserButtonAfterChoose.click(); + } + + public async operateFlexTab(desiredTab: string, desiredState: boolean): Promise { + // desiredState true=open false=closed + const locator: { [K: string]: Locator } = { + channelSettings: this.channelSettings, + messageSearchBar: this.messageSearchBar, + avatarImage: this.avatarImage, + notificationsSettings: this.notificationsSettings, + filesTabContent: this.filesTabContent, + mentionsTabContent: this.mentionsTabContent, + starredTabContent: this.starredTabContent, + pinnedTabContent: this.pinnedTabContent, + channelTab: this.channelTab, + searchTab: this.searchTab, + membersTab: this.membersTab, + notificationsTab: this.notificationsTab, + filesTab: this.filesTab, + mentionsTab: this.mentionsTab, + starredTab: this.starredTab, + pinnedTab: this.pinnedTab, + }; + + const operate = async (tab: string, panel: string, more: boolean): Promise => { + // this[panel].should(!desiredState ? 'be.visible' : 'not.exist'); + if (!desiredState) { + await expect(locator[panel]).toBeVisible(); + } else { + await expect(locator[panel]).not.toBeVisible(); + } + + if (more) { + await this.headerMoreActions.click(); + } + + await locator[tab].click(); + + // The button "more" keeps the focus when popover is closed from a click + // on an item, need to click again to change the status to unselected and + // allow the next click to open the popover again + if (more) { + await this.headerMoreActions.click(); + } + + if (desiredState) { + await expect(locator[panel]).toBeVisible(); + } else { + await expect(locator[panel]).not.toBeVisible(); + } + }; + + const tabs: { [K: string]: Function } = { + info: async (): Promise => { + await operate('channelTab', 'channelSettings', false); + }, + + search: async (): Promise => { + await operate('searchTab', 'messageSearchBar', false); + }, + + members: async (): Promise => { + await operate('membersTab', 'avatarImage', false); + }, + + notifications: async (): Promise => { + await operate('notificationsTab', 'notificationsSettings', true); + }, + + files: async (): Promise => { + await operate('filesTab', 'filesTabContent', false); + }, + + mentions: async (): Promise => { + await operate('mentionsTab', 'mentionsTabContent', true); + }, + + starred: async (): Promise => { + await operate('starredTab', 'starredTabContent', true); + }, + + pinned: async (): Promise => { + await operate('pinnedTab', 'pinnedTabContent', true); + }, + }; + + const callFunctionTabs = async (name: string): Promise => { + return tabs[name](); + }; + + await callFunctionTabs(desiredTab); + } + + get flexTabViewThreadMessage(): Locator { + return this.page.locator( + 'div.thread-list.js-scroll-thread ul.thread [data-qa-type="message"]:last-child div.message-body-wrapper [data-qa-type="message-body"]', + ); + } + + public async doAddRole(role: string): Promise { + await this.usersAddUserRoleList.click(); + await this.page.locator(`li[value=${role}]`).click(); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/Global.ts b/apps/meteor/tests/e2e/pageobjects/Global.ts new file mode 100644 index 000000000000..1b0ee13cd91a --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/Global.ts @@ -0,0 +1,54 @@ +import { Locator } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class Global extends BasePage { + get modalConfirm(): Locator { + return this.page.locator('.rcx-modal .rcx-button--primary-danger'); + } + + get modalFilePreview(): Locator { + return this.page.locator('.rc-modal .upload-preview-file'); + } + + get getToastBar(): Locator { + return this.page.locator('.rcx-toastbar'); + } + + get getToastBarError(): Locator { + return this.page.locator('.rcx-toastbar.rcx-toastbar--error'); + } + + get getToastBarSuccess(): Locator { + return this.page.locator('.rcx-toastbar.rcx-toastbar--success'); + } + + get flexNav(): Locator { + return this.page.locator('.flex-nav'); + } + + public async confirmPopup(): Promise { + await this.modalConfirm.waitFor(); + await this.modalConfirm.click(); + } + + public async dismissToastBar(): Promise { + await this.getToastBar.locator('button').click(); + } + + get modal(): Locator { + return this.page.locator('#modal-root'); + } + + get btnModalCancel(): Locator { + return this.page.locator( + '#modal-root div dialog div.rcx-modal__inner div.rcx-modal__footer div div button.rcx-button--ghost.rcx-button', + ); + } + + get btnModalRemove(): Locator { + return this.page.locator( + '#modal-root div dialog div.rcx-modal__inner div.rcx-modal__footer div div button.rcx-button--primary-danger.rcx-button', + ); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/LoginPage.ts b/apps/meteor/tests/e2e/pageobjects/LoginPage.ts new file mode 100644 index 000000000000..4c1ea2e92325 --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/LoginPage.ts @@ -0,0 +1,119 @@ +import { Locator, expect } from '@playwright/test'; + +import { ILogin, IRegister } from '../utils/interfaces/Login'; +import { BasePage } from './BasePage'; +import { HOME_SELECTOR, REGISTER_STEP2_BUTTON } from '../utils/mocks/waitSelectorsMock'; + +export class LoginPage extends BasePage { + private get registerButton(): Locator { + return this.page.locator('button.register'); + } + + private get forgotPasswordButton(): Locator { + return this.page.locator('.forgot-password'); + } + + get submitButton(): Locator { + return this.page.locator('.login'); + } + + get registerNextButton(): Locator { + return this.page.locator('button[data-loading-text=" Please_wait ..."]'); + } + + get emailOrUsernameField(): Locator { + return this.page.locator('[name=emailOrUsername]'); + } + + get nameField(): Locator { + return this.page.locator('[name=name]'); + } + + get emailField(): Locator { + return this.page.locator('[name=email]'); + } + + get passwordField(): Locator { + return this.page.locator('[name=pass]'); + } + + get confirmPasswordField(): Locator { + return this.page.locator('[name=confirm-pass]'); + } + + get nameInvalidText(): Locator { + return this.page.locator('[name=name]~.input-error'); + } + + get emailInvalidText(): Locator { + return this.page.locator('[name=email]~.input-error'); + } + + get passwordInvalidText(): Locator { + return this.page.locator('[name=pass]~.input-error'); + } + + get confirmPasswordInvalidText(): Locator { + return this.page.locator('[name=confirm-pass]~.input-error'); + } + + get getSideBarAvatarButton(): Locator { + return this.page.locator('[data-qa="sidebar-avatar-button"]'); + } + + public async gotToRegister(): Promise { + await this.registerButton.click(); + } + + public async gotToForgotPassword(): Promise { + await this.forgotPasswordButton.click(); + } + + public async registerNewUser({ name, email, password }: IRegister): Promise { + await this.nameField.type(name); + await this.emailField.type(email); + await this.passwordField.type(password); + await this.confirmPasswordField.type(password); + await this.submit(); + + await this.waitForSelector(REGISTER_STEP2_BUTTON); + await this.registerNextButton.click(); + await this.waitForSelector(HOME_SELECTOR); + } + + public async login({ email, password }: ILogin): Promise { + await this.emailOrUsernameField.type(email); + await this.passwordField.type(password); + await this.submitButton.click(); + } + + public async submit(): Promise { + await this.submitButton.click(); + } + + public async registerFail(): Promise { + await this.gotToRegister(); + await this.submit(); + + await expect(this.nameInvalidText).toBeVisible(); + await expect(this.emailInvalidText).toBeVisible(); + await expect(this.passwordInvalidText).toBeVisible(); + } + + public async registerFailWithDifferentPassword({ name, email, password }: IRegister, invalidPassword: string): Promise { + await this.gotToRegister(); + await this.passwordField.type(password); + await this.emailField.type(email); + await this.nameField.type(name); + await this.confirmPasswordField.type(invalidPassword); + + await this.submit(); + await expect(this.confirmPasswordInvalidText).toBeVisible(); + await expect(this.confirmPasswordInvalidText).toHaveText('The password confirmation does not match password'); + } + + public async logout(): Promise { + await this.getSideBarAvatarButton.click(); + await this.page.locator('li.rcx-option >> text="Logout"').click(); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/MainContent.ts b/apps/meteor/tests/e2e/pageobjects/MainContent.ts new file mode 100644 index 000000000000..c3b098d0c4fb --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/MainContent.ts @@ -0,0 +1,345 @@ +import fs from 'fs'; + +import { expect, Locator } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class MainContent extends BasePage { + get mainContent(): Locator { + return this.page.locator('.main-content'); + } + + get emptyFavoriteStar(): Locator { + return this.page.locator('//*[contains(@class, "rcx-room-header")]//*[contains(@class, "rcx-icon--name-star")]'); + } + + get favoriteStar(): Locator { + return this.page.locator('//*[contains(@class, "rcx-room-header")]//*[contains(@class, "rcx-icon--name-star-filled")]'); + } + + public channelTitle(title: string): Locator { + return this.page.locator('.rcx-room-header', { hasText: title }); + } + + get messageInput(): Locator { + return this.page.locator('[name="msg"]'); + } + + get sendBtn(): Locator { + return this.page.locator('.rc-message-box__icon.js-send'); + } + + get messageBoxActions(): Locator { + return this.page.locator('(//*[contains(@class, "rc-message-box__icon")])[1]'); + } + + get recordBtn(): Locator { + return this.page.locator('[data-qa-id="audio-record"]'); + } + + get emojiBtn(): Locator { + return this.page.locator('.rc-message-box__icon.emoji-picker-icon'); + } + + get messagePopUp(): Locator { + return this.page.locator('.message-popup'); + } + + get messagePopUpTitle(): Locator { + return this.page.locator('.message-popup-title'); + } + + get messagePopUpItems(): Locator { + return this.page.locator('.message-popup-items'); + } + + get messagePopUpFirstItem(): Locator { + return this.page.locator('.popup-item.selected'); + } + + get lastMessageUser(): Locator { + return this.page.locator('.message:last-child div:nth-child(2) button'); + } + + get lastMessageFileName(): Locator { + return this.page.locator('[data-qa-type="message"]:last-child div:nth-child(3) div:nth-child(2) div a:nth-child(1)'); + } + + get lastMessage(): Locator { + return this.page.locator('.messages-box [data-qa-type="message"]').last(); + } + + get lastMessageRoleAdded(): Locator { + return this.page.locator('.message:last-child.subscription-role-added .body'); + } + + get lastMessageUserTag(): Locator { + return this.page.locator('.message:last-child .role-tag'); + } + + get lastMessageForMessageTest(): Locator { + return this.page.locator('[data-qa-type="message"]:last-child div.message-body-wrapper div:nth-child(2)'); + } + + get messageOptionsBtns(): Locator { + return this.page.locator('.message:last-child .message-actions'); + } + + get messageReply(): Locator { + return this.page.locator('[data-qa-id="reply-in-thread"]'); + } + + get messageEdit(): Locator { + return this.page.locator('[data-qa-id="edit-message"]'); + } + + get messageDelete(): Locator { + return this.page.locator('[data-qa-id="delete-message"]'); + } + + get messagePermalink(): Locator { + return this.page.locator('[data-qa-id="permalink"]'); + } + + get messageCopy(): Locator { + return this.page.locator('[data-qa-id="copy"]'); + } + + get messageQuote(): Locator { + return this.page.locator('[data-qa-id="quote-message"]'); + } + + get messageStar(): Locator { + return this.page.locator('[data-qa-id="star-message"]'); + } + + get messageUnread(): Locator { + return this.page.locator('[data-id="mark-message-as-unread"][data-type="message-action"]'); + } + + get emojiPickerMainScreen(): Locator { + return this.page.locator('.emoji-picker'); + } + + get emojiPickerPeopleIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-people")]'); + } + + get emojiPickerNatureIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-nature")]'); + } + + get emojiPickerFoodIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-food")]'); + } + + get emojiPickerActivityIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-activity")]'); + } + + get emojiPickerTravelIcon(): Locator { + return this.page.locator('.emoji-picker .icon-travel'); + } + + get emojiPickerObjectsIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-objects")]'); + } + + get emojiPickerSymbolsIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-symbols")]'); + } + + get emojiPickerFlagsIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-flags")]'); + } + + get emojiPickerCustomIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-rocket")]'); + } + + get emojiPickerFilter(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "js-emojipicker-search")]'); + } + + get emojiPickerChangeTone(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "change-tone")]'); + } + + get emojiGrinning(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "emoji-grinning")]'); + } + + get emojiSmile(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "emoji-smile")]'); + } + + get modalTitle(): Locator { + return this.page.locator('#modal-root .rcx-modal__title'); + } + + get modalCancelButton(): Locator { + return this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--ghost'); + } + + get modalDeleteMessageButton(): Locator { + return this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--primary-danger'); + } + + get buttonSend(): Locator { + return this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--primary'); + } + + get modalFilePreview(): Locator { + return this.page.locator( + '//div[@id="modal-root"]//header//following-sibling::div[1]//div//div//img | //div[@id="modal-root"]//header//following-sibling::div[1]//div//div//div//i', + ); + } + + get fileName(): Locator { + return this.page.locator('//div[@id="modal-root"]//fieldset//div[1]//label'); + } + + get fileDescription(): Locator { + return this.page.locator('//div[@id="modal-root"]//fieldset//div[2]//label'); + } + + public async waitForLastMessageEqualsHtml(text: string): Promise { + await expect(this.page.locator('(//*[contains(@class, "message") and contains(@class, "body")])[last()]')).toContainText(text); + } + + public async waitForLastMessageEqualsText(text: string): Promise { + await expect(this.page.locator('(//*[contains(@class, "message") and contains(@class, "body")])[last()]')).toContainText(text); + } + + public async sendMessage(text: string): Promise { + await this.setTextToInput(text); + await this.keyboardPress('Enter'); + } + + public async addTextToInput(text: any): Promise { + await this.messageInput.type(text); + } + + public async setTextToInput(text: string, options: { delay?: number } = {}): Promise { + await this.messageInput.click({ clickCount: 3 }); + await this.page.keyboard.press('Backspace'); + await this.messageInput.type(text, { delay: options.delay ?? 0 }); + } + + public async dragAndDropFile(): Promise { + const contract = await fs.promises.readFile('./tests/e2e/utils/fixtures/any_file.txt', 'utf-8'); + + const dataTransfer = await this.page.evaluateHandle((contract) => { + const data = new DataTransfer(); + const file = new File([`${contract}`], 'any_file.txt', { + type: 'text/plain', + }); + data.items.add(file); + return data; + }, contract); + + await this.page.dispatchEvent( + 'div.dropzone-overlay.dropzone-overlay--enabled.background-transparent-darkest.color-content-background-color', + 'drop', + { dataTransfer }, + ); + } + + public async sendFileClick(): Promise { + await this.buttonSend.click(); + } + + get descriptionInput(): Locator { + return this.page.locator('//div[@id="modal-root"]//fieldset//div[2]//span//input'); + } + + get fileNameInput(): Locator { + return this.page.locator('//div[@id="modal-root"]//fieldset//div[1]//span//input'); + } + + public async setFileName(): Promise { + await this.fileNameInput.fill('any_file1.txt'); + } + + public async setDescription(): Promise { + await this.descriptionInput.type('any_description'); + } + + get getFileDescription(): Locator { + return this.page.locator('[data-qa-type="message"]:last-child div:nth-child(3) div:nth-child(2) div p'); + } + + public async selectAction(action: string): Promise { + switch (action) { + case 'edit': + await this.messageEdit.click(); + await this.messageInput.fill('this message was edited'); + await this.keyboardPress('Enter'); + await expect(this.lastMessageForMessageTest).toHaveText('this message was edited'); + break; + case 'reply': + this.messageReply.click(); + break; + case 'delete': + await this.messageDelete.click(); + await this.acceptDeleteMessage(); + await expect(this.lastMessageForMessageTest).not.toHaveText('Message for Message Delete Tests'); + break; + case 'permalink': + await this.messagePermalink.click(); + break; + case 'copy': + await this.messageCopy.click(); + break; + case 'quote': + await this.messageQuote.click(); + await this.messageInput.type('this is a quote message'); + await this.keyboardPress('Enter'); + break; + case 'star': + await this.messageStar.click(); + await expect(this.page.locator('div.rcx-toastbar:has-text("Message has been starred")')).toBeVisible(); + break; + case 'unread': + await this.messageUnread.click(); + break; + case 'reaction': + await this.messageReply.click(); + await this.emojiPickerPeopleIcon.click(); + await this.emojiGrinning.click(); + break; + } + } + + public async openMessageActionMenu(): Promise { + await this.page.locator('.messages-box [data-qa-type="message"]:last-child').hover(); + await this.page.locator('[data-qa-type="message"]:last-child div.message-actions__menu').waitFor(); + await this.page.locator('[data-qa-type="message"]:last-child div.message-actions__menu').click(); + } + + public async openMoreActionMenu(): Promise { + await this.page.locator('.rc-message-box [data-qa-id="menu-more-actions"]').click(); + await this.page.waitForSelector('.rc-popover__content'); + } + + public async acceptDeleteMessage(): Promise { + await this.modalDeleteMessageButton.click(); + } + + get waitForLastMessageTextAttachmentEqualsText(): Locator { + return this.page.locator('[data-qa-type="message"]:last-child .rcx-attachment__details .rcx-box--with-inline-elements'); + } + + get userCard(): Locator { + return this.page.locator('[data-qa="UserCard"]'); + } + + get viewUserProfile(): Locator { + return this.page.locator('[data-qa="UserCard"] a'); + } + + public async doReload(): Promise { + await this.page.reload({ waitUntil: 'load' }); + await this.page.waitForSelector('.messages-box'); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/PreferencesMainContent.ts b/apps/meteor/tests/e2e/pageobjects/PreferencesMainContent.ts new file mode 100644 index 000000000000..6111ebed34f6 --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/PreferencesMainContent.ts @@ -0,0 +1,46 @@ +import { expect, Locator } from '@playwright/test'; + +import { BasePage } from './BasePage'; + +export class PreferencesMainContent extends BasePage { + get realNameTextInput(): Locator { + return this.page.locator('//label[contains(text(), "Name")]/..//input'); + } + + get userNameTextInput(): Locator { + return this.page.locator('//label[contains(text(), "Username")]/..//input'); + } + + get emailTextInput(): Locator { + return this.page.locator('//label[contains(text(), "Email")]/..//input'); + } + + get passwordTextInput(): Locator { + return this.page.locator('//label[contains(text(), "Password")]/..//input'); + } + + get avatarFileInput(): Locator { + return this.page.locator('.avatar-file-input'); + } + + get useUploadedAvatar(): Locator { + return this.page.locator('.avatar-suggestion-item:nth-of-type(2) .select-service'); + } + + get submitBtn(): Locator { + return this.page.locator('[data-qa="AccountProfilePageSaveButton"]'); + } + + public async changeUsername(userName: string): Promise { + await this.userNameTextInput.fill(userName); + } + + public async changeRealName(realName: string): Promise { + await this.realNameTextInput.fill(realName); + } + + public async saveChanges(): Promise { + await expect(this.submitBtn).toBeEnabled(); + await this.submitBtn.click(); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/SetupWizard.ts b/apps/meteor/tests/e2e/pageobjects/SetupWizard.ts new file mode 100644 index 000000000000..89ce5c70e457 --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/SetupWizard.ts @@ -0,0 +1,172 @@ +import { expect, Locator } from '@playwright/test'; + +import { BasePage } from './BasePage'; +import { reason, INVALID_EMAIL_WITHOUT_MAIL_PROVIDER } from '../utils/mocks/userAndPasswordMock'; +import { IRegister } from '../utils/interfaces/Login'; +import { BACKSPACE } from '../utils/mocks/keyboardKeyMock'; + +export class SetupWizard extends BasePage { + private get nextStep(): Locator { + return this.page.locator('//button[contains(text(), "Next")]'); + } + + private get fullName(): Locator { + return this.page.locator('[name="fullname"]'); + } + + private get userName(): Locator { + return this.page.locator('[name="username"]'); + } + + private get companyEmail(): Locator { + return this.page.locator('[name="companyEmail"]'); + } + + private get password(): Locator { + return this.page.locator('[name="password"]'); + } + + get goToWorkspace(): Locator { + return this.page.locator('//button[contains(text(), "Confirm")]'); + } + + get organizationType(): Locator { + return this.page.locator('[name="organizationType"]'); + } + + get organizationTypeSelect(): Locator { + return this.page.locator('.rcx-options .rcx-option:first-child'); + } + + get organizationName(): Locator { + return this.page.locator('[name="organizationName"]'); + } + + get industry(): Locator { + return this.page.locator('[name="organizationIndustry"]'); + } + + get industrySelect(): Locator { + return this.page.locator('.rcx-options .rcx-option:first-child'); + } + + get size(): Locator { + return this.page.locator('[name="organizationSize"]'); + } + + get sizeSelect(): Locator { + return this.page.locator('.rcx-options .rcx-option:first-child'); + } + + get country(): Locator { + return this.page.locator('[name="country"]'); + } + + get countrySelect(): Locator { + return this.page.locator('.rcx-options .rcx-option:first-child'); + } + + get registeredServer(): Locator { + return this.page.locator('input[name=email]'); + } + + get registerButton(): Locator { + return this.page.locator('//button[contains(text(), "Register")]'); + } + + get agreementField(): Locator { + return this.page.locator('//input[@name="agreement"]/../i[contains(@class, "rcx-check-box")]'); + } + + get standaloneServer(): Locator { + return this.page.locator('//button[contains(text(), "Continue as standalone")]'); + } + + get standaloneConfirmText(): Locator { + return this.page.locator('//*[contains(text(), "Standalone Server Confirmation")]'); + } + + get fullNameInvalidText(): Locator { + return this.page.locator('//input[@name="fullname"]/../following-sibling::span'); + } + + get userNameInvalidText(): Locator { + return this.page.locator('//input[@name="username"]/../following-sibling::span'); + } + + get companyEmailInvalidText(): Locator { + return this.page.locator('//input[@name="companyEmail"]/../following-sibling::span'); + } + + get passwordInvalidText(): Locator { + return this.page.locator('//input[@name="password"]/../../../span[contains(@class, "rcx-field__error")]'); + } + + get industryInvalidSelect(): Locator { + return this.page.locator('//div[@name="organizationIndustry"]/../following-sibling::span'); + } + + get sizeInvalidSelect(): Locator { + return this.page.locator('//div[@name="organizationSize"]/../following-sibling::span'); + } + + get countryInvalidSelect(): Locator { + return this.page.locator('//div[@name="country"]/../following-sibling::span'); + } + + get stepThreeInputInvalidMail(): Locator { + return this.page.locator('//input[@name="email"]/../../span[contains(text(), "This field is required")]'); + } + + public async stepTwoSuccess(): Promise { + await this.organizationName.type(reason); + await this.organizationType.click(); + await this.organizationTypeSelect.click(); + await expect(this.page.locator('.rcx-options')).toHaveCount(0); + await this.industry.click(); + await this.industrySelect.click(); + await expect(this.page.locator('.rcx-options')).toHaveCount(0); + await this.size.click(); + await this.sizeSelect.click(); + await expect(this.page.locator('.rcx-options')).toHaveCount(0); + await this.country.click(); + await this.countrySelect.click(); + await this.nextStep.click(); + } + + public async stepThreeSuccess(): Promise { + await this.standaloneServer.click(); + } + + public async stepOneFailedBlankFields(): Promise { + await this.nextStep.click(); + await expect(this.fullNameInvalidText).toBeVisible(); + await expect(this.userNameInvalidText).toBeVisible(); + await expect(this.companyEmailInvalidText).toBeVisible(); + await expect(this.passwordInvalidText).toBeVisible(); + } + + public async stepOneFailedWithInvalidEmail(adminCredentials: IRegister): Promise { + await this.fullName.type(adminCredentials.name); + await this.userName.type(adminCredentials.name); + await this.companyEmail.type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); + await this.password.type(adminCredentials.password); + await this.nextStep.click(); + await expect(this.companyEmail).toBeFocused(); + } + + public async stepTwoFailedWithBlankFields(): Promise { + await this.nextStep.click(); + await expect(this.organizationName).toBeVisible(); + await expect(this.industryInvalidSelect).toBeVisible(); + await expect(this.sizeInvalidSelect).toBeVisible(); + await expect(this.countryInvalidSelect).toBeVisible(); + } + + public async stepThreeFailedWithInvalidField(): Promise { + await this.registeredServer.type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); + await this.registeredServer.click({ clickCount: 3 }); + await this.keyboardPress(BACKSPACE); + await expect(this.stepThreeInputInvalidMail).toBeVisible(); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/SideNav.ts b/apps/meteor/tests/e2e/pageobjects/SideNav.ts new file mode 100644 index 000000000000..cc80467929ab --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/SideNav.ts @@ -0,0 +1,180 @@ +import { expect, Locator } from '@playwright/test'; + +import { BasePage } from './BasePage'; +import { ENTER } from '../utils/mocks/keyboardKeyMock'; + +export class SideNav extends BasePage { + get channelType(): Locator { + return this.page.locator( + '//*[@id="modal-root"]//*[contains(@class, "rcx-field") and contains(text(), "Private")]/../following-sibling::label/i', + ); + } + + get channelName(): Locator { + return this.page.locator('#modal-root [placeholder="Channel Name"]'); + } + + get saveChannelBtn(): Locator { + return this.page.locator('//*[@id="modal-root"]//button[contains(text(), "Create")]'); + } + + get sidebarUserMenu(): Locator { + return this.page.locator('[data-qa="sidebar-avatar-button"]'); + } + + get statusOnline(): Locator { + return this.page.locator('(//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "online")])[1]'); + } + + get statusAway(): Locator { + return this.page.locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "away")]'); + } + + get statusBusy(): Locator { + return this.page.locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "busy")]'); + } + + get statusOffline(): Locator { + return this.page.locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "offline")]'); + } + + get account(): Locator { + return this.page.locator('//li[@class="rcx-option"]//div[contains(text(), "My Account")]'); + } + + get admin(): Locator { + return this.page.locator('//li[@class="rcx-option"]//div[contains(text(), "Administration")]'); + } + + get omnichannel(): Locator { + return this.page.locator('li.rcx-option >> text="Omnichannel"'); + } + + get users(): Locator { + return this.page.locator('.flex-nav [href="/admin/users"]'); + } + + get logout(): Locator { + return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Logout")]'); + } + + get sideNavBar(): Locator { + return this.page.locator('.sidebar'); + } + + get flexNav(): Locator { + return this.page.locator('.flex-nav'); + } + + get spotlightSearchIcon(): Locator { + return this.page.locator('[data-qa="sidebar-search"]'); + } + + get spotlightSearch(): Locator { + return this.page.locator('[data-qa="sidebar-search-input"]'); + } + + get spotlightSearchPopUp(): Locator { + return this.page.locator('[data-qa="sidebar-search-result"]'); + } + + get newChannelBtnToolbar(): Locator { + return this.page.locator('[data-qa="sidebar-create"]'); + } + + get newChannelBtn(): Locator { + return this.page.locator('li.rcx-option >> text="Channel"'); + } + + get general(): Locator { + return this.getChannelFromList('general'); + } + + get preferences(): Locator { + return this.page.locator('[href="/account/preferences"]'); + } + + get profile(): Locator { + return this.page.locator('[href="/account/profile"]'); + } + + get preferencesClose(): Locator { + return this.page.locator('//*[contains(@class,"flex-nav")]//i[contains(@class, "rcx-icon--name-cross")]'); + } + + get burgerBtn(): Locator { + return this.page.locator('[data-qa-id="burger-menu"]'); + } + + get firstSidebarItemMenu(): Locator { + return this.page.locator('[data-qa=sidebar-avatar-button]'); + } + + get returnToMenuInLowResolution(): Locator { + return this.page.locator('//button[@aria-label="Close menu"]'); + } + + public async isSideBarOpen(): Promise { + return !!(await this.sideNavBar.getAttribute('style')); + } + + public async openChannel(channelName: string): Promise { + await this.page.locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).scrollIntoViewIfNeeded(); + await this.page.locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).click(); + await expect(this.page.locator('.rcx-room-header')).toContainText(channelName); + } + + public async searchChannel(channelName: string): Promise { + await expect(this.spotlightSearch).toBeVisible(); + + await this.spotlightSearch.click(); + + await expect(this.spotlightSearch).toBeFocused(); + await this.spotlightSearch.type(channelName); + + await expect(this.page.locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).first()).toContainText(channelName); + + await this.spotlightSearchPopUp.click(); + } + + public getChannelFromList(channelName: any): Locator { + return this.page.locator('[data-qa="sidebar-item-title"]', { hasText: channelName }); + } + + get searchUser(): Locator { + return this.page.locator('[data-qa="sidebar-search"]'); + } + + get searchInput(): Locator { + return this.page.locator('[data-qa="sidebar-search-input"]'); + } + + public async createChannel(channelName: any, isPrivate: any /* isReadOnly*/): Promise { + await this.newChannelBtnToolbar.click(); + + await this.newChannelBtn.click(); + + if (!isPrivate) { + await this.channelType.click(); + } + + await this.channelName.type(channelName); + + await expect(this.saveChannelBtn).toBeEnabled(); + + await this.saveChannelBtn.click(); + await expect(this.channelType).not.toBeVisible(); + } + + public async findForChat(target: string): Promise { + await this.searchUser.click(); + await this.searchInput.type(target, { delay: 100 }); + await this.page.keyboard.press(ENTER); + } + + public async doLogout(): Promise { + await this.page.goto('/home'); + await this.sidebarUserMenu.click(); + await this.logout.click(); + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/index.ts b/apps/meteor/tests/e2e/pageobjects/index.ts new file mode 100644 index 000000000000..7a9bcd67006e --- /dev/null +++ b/apps/meteor/tests/e2e/pageobjects/index.ts @@ -0,0 +1,12 @@ +export * from './Administration'; +export * from './Agents'; +export * from './ChannelCreation'; +export * from './FlexTab'; +export * from './Global'; +export * from './LoginPage'; +export * from './MainContent'; +export * from './PreferencesMainContent'; +export * from './SetupWizard'; +export * from './SideNav'; +export * from './Discussion'; +export * from './Departments'; diff --git a/apps/meteor/tests/e2e/utils/pageobjects/Administration.ts b/apps/meteor/tests/e2e/utils/pageobjects/Administration.ts deleted file mode 100644 index b5470b5d629d..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/Administration.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { Locator, expect } from '@playwright/test'; - -import BasePage from './BasePage'; - -export default class Administration extends BasePage { - public settingsSearch(): Locator { - return this.getPage().locator('input[type=search]'); - } - - public settingsLink(): Locator { - return this.getPage().locator('.flex-nav [href="/admin/settings"]'); - } - - public groupSettingsPageBack(): Locator { - return this.getPage().locator('button[title="Back"]'); - } - - public infoLink(): Locator { - return this.getPage().locator('//div[contains(text(),"Info")]'); - } - - public roomsLink(): Locator { - return this.getPage().locator('.flex-nav [href="/admin/rooms"]'); - } - - public usersLink(): Locator { - return this.getPage().locator('.flex-nav [href="/admin/users"]'); - } - - public accountSettingsButton(): Locator { - return this.getPage().locator('[data-qa-id="Accounts"] button'); - } - - public generalSettingsButton(): Locator { - return this.getPage().locator('[data-qa-id="General"] button'); - } - - public layoutSettingsButton(): Locator { - return this.getPage().locator('[data-qa-id="Layout"] button'); - } - - public permissionsLink(): Locator { - return this.getPage().locator('.flex-nav [href="/admin/permissions"]'); - } - - public infoDeployment(): Locator { - return this.getPage().locator('//div[text()="Deployment"]'); - } - - public infoLicense(): Locator { - return this.getPage().locator('//div[text()="License"]'); - } - - public infoUsage(): Locator { - return this.getPage().locator('//div[text()="Usage"]'); - } - - public infoFederation(): Locator { - return this.getPage().locator('//section[@data-qa="admin-info"]//div[text()="Federation"]'); - } - - public roomsSearchForm(): Locator { - return this.getPage().locator('input[placeholder ="Search Rooms"]'); - } - - public async verifyCheckBoxRendered(checkBoxes: string[]): Promise { - const expected = []; - for (const checkBox of checkBoxes) { - expected.push(expect(this.adminCheckBox(checkBox)).toBeVisible()); - } - await Promise.all(expected); - } - - public roomsGeneralChannel(): Locator { - return this.getPage().locator('//table//tbody//tr[1]//..//div//div//div//div[text()="general"]'); - } - - public notFoundChannelOrUser(): Locator { - return this.getPage().locator("//div[text()='No data found']"); - } - - public notFoundChannels(): Locator { - return this.getPage().locator("//div[text()='No results found']"); - } - - public usersFilter(): Locator { - return this.getPage().locator('input[placeholder="Search Users"]'); - } - - public userInTable(id: string): Locator { - return this.getPage().locator(`tr > td:has-text("${id}")`); - } - - public rolesSettingsFindInput(): Locator { - return this.getPage().locator('[data-qa="PermissionTable-PermissionsTableFilter"]'); - } - - public rolesSettingsTab(): Locator { - return this.getPage().locator('[data-qa="PermissionTable-Settings"]'); - } - - public homeTitleInput(): Locator { - return this.getPage().locator('[data-qa-setting-id="Layout_Home_Title"]'); - } - - public buttonSave(): Locator { - return this.getPage().locator('button.save'); - } - - public generalSectionIframeIntegration(): Locator { - return this.getPage().locator('[data-qa-section="Iframe_Integration"]'); - } - - public generalIframeSendTargetOrigin(): Locator { - return this.getPage().locator('[data-qa-setting-id="Iframe_Integration_send_target_origin"]'); - } - - public generalSectionNotifications(): Locator { - return this.getPage().locator('[data-qa-section="Notifications"]'); - } - - public generalSectionRestApi(): Locator { - return this.getPage().locator('[data-qa-section="REST API"]'); - } - - public generalSectionReporting(): Locator { - return this.getPage().locator('[data-qa-section="Reporting"]'); - } - - public generalSectionStreamCast(): Locator { - return this.getPage().locator('[data-qa-section="Stream_Cast"]'); - } - - public generalSectionUTF8(): Locator { - return this.getPage().locator('[data-qa-section="UTF8"]'); - } - - public generalSiteUrl(): Locator { - return this.getPage().locator('[data-qa-setting-id="Site_Url"]'); - } - - public generalSiteUrlReset(): Locator { - return this.getPage().locator('//label[@title="Site_Url"]//following-sibling::button'); - } - - public generalSiteName(): Locator { - return this.getPage().locator('[data-qa-setting-id="Site_Name"]'); - } - - public generalSiteNameReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="Site_Name"]'); - } - - public generalHomeTitleReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="Layout_Home_Title"]'); - } - - public generalLanguage(): Locator { - return this.getPage().locator('[data-qa-setting-id="Language"]'); - } - - public generalSelfSignedCerts(): Locator { - return this.getPage().locator('//label[@data-qa-setting-id="Allow_Invalid_SelfSigned_Certs"]//i'); - } - - public generalSelfSignedCertsReset(): Locator { - return this.getPage().locator('//button[@data-qa-reset-setting-id="Allow_Invalid_SelfSigned_Certs"]'); - } - - public generalFavoriteRoom(): Locator { - return this.getPage().locator('[data-qa-setting-id="Favorite_Rooms"]'); - } - - public generalFavoriteRoomReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="Favorite_Rooms"]'); - } - - public generalCdnPrefix(): Locator { - return this.getPage().locator('[data-qa-setting-id="CDN_PREFIX"]'); - } - - public generalCdnPrefixReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="CDN_PREFIX"]'); - } - - public generalForceSSL(): Locator { - return this.getPage().locator('[data-qa-setting-id="Force_SSL"]'); - } - - public generalForceSSLReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="Force_SSL"]'); - } - - public generalGoogleTagId(): Locator { - return this.getPage().locator('[data-qa-setting-id="GoogleTagManager_id"]'); - } - - public generalGoogleTagIdReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="GoogleTagManager_id"]'); - } - - public generalBugsnagKey(): Locator { - return this.getPage().locator('[data-qa-setting-id="Bugsnag_api_key"]'); - } - - public generalBugsnagKeyReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="Bugsnag_api_key"]'); - } - - public robotsFileContents(): Locator { - return this.getPage().locator('#Robot_Instructions_File_Content'); - } - - public robotsFileContentsReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="Robot_Instructions_File_Content"]'); - } - - public defaultReferrerPolicy(): Locator { - return this.getPage().locator('//div[@data-qa-setting-id="Default_Referrer_Policy"]'); - } - - public defaultReferrerPolicyReset(): Locator { - return this.getPage().locator('[data-qa-reset-setting-id="Default_Referrer_Policy"]'); - } - - public defaultReferrerPolicyOptions(): Locator { - return this.getPage().locator('.rcx-options .rcx-option:first-child'); - } - - public generalIframeSend(): Locator { - return this.getPage().locator('[data-qa-setting-id="Iframe_Integration_send_enable"]'); - } - - public generalIframeReceive(): Locator { - return this.getPage().locator('[data-qa-setting-id="Iframe_Integration_receive_enable"]'); - } - - public generalIframeReceiveOrigin(): Locator { - return this.getPage().locator('[data-qa-setting-id="Iframe_Integration_receive_origin"]'); - } - - public generalNotificationsMaxRoomMembers(): Locator { - return this.getPage().locator('[data-qa-setting-id="Notifications_Max_Room_Members"]'); - } - - public generalRestApiUserLimit(): Locator { - return this.getPage().locator('[data-qa-setting-id="API_User_Limit"]'); - } - - public generalReporting(): Locator { - return this.getPage().locator('[data-qa-setting-id="Statistics_reporting"]'); - } - - public generalStreamCastAddress(): Locator { - return this.getPage().locator('[data-qa-setting-id="Stream_Cast_Address"]'); - } - - public generalUTF8UsernamesRegex(): Locator { - return this.getPage().locator('[data-qa-setting-id="UTF8_User_Names_Validation"]'); - } - - public generalUTF8ChannelsRegex(): Locator { - return this.getPage().locator('[data-qa-setting-id="UTF8_Channel_Names_Validation"]'); - } - - public generalUTF8NamesSlug(): Locator { - return this.getPage().locator('[data-qa-setting-id="UTF8_Names_Slugify"]'); - } - - public accountsSectionDefaultUserPreferences(): Locator { - return this.getPage().locator('[data-qa-section="Accounts_Default_User_Preferences"]'); - } - - public accountsEnableAutoAway(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_enableAutoAway"]'); - } - - public accountsIdleTimeLimit(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_idleTimeLimit"]'); - } - - public accountsDesktopNotifications(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_desktopNotifications"]'); - } - - public accountsMobileNotifications(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_pushNotifications"]'); - } - - public accountsUnreadAlert(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_unreadAlert"]'); - } - - public accountsConvertAsciiEmoji(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_convertAsciiEmoji"]'); - } - - public accountsAutoImageLoad(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_autoImageLoad"]'); - } - - public accountsSaveMobileBandwidth(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_saveMobileBandwidth"]'); - } - - public accountsCollapseMediaByDefault(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_collapseMediaByDefault"]'); - } - - public accountsHideUsernames(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_hideUsernames"]'); - } - - public accountsHideRoles(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_hideRoles"]'); - } - - public accountsHideFlexTab(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_hideFlexTab"]'); - } - - public accountsDisplayAvatars(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_displayAvatars"]'); - } - - public accountsSendOnEnter(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_sendOnEnter"]'); - } - - public accountsMessageViewMode(): Locator { - return this.getPage().locator('//div[@data-qa-setting-id="Accounts_Default_User_Preferences_messageViewMode"]//div//span'); - } - - public accountsEmailNotificationMode(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_emailNotificationMode"]'); - } - - public accountsNewRoomNotification(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_newRoomNotification"]'); - } - - public accountsNewMessageNotification(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_newMessageNotification"]'); - } - - public accountsNotificationsSoundVolume(): Locator { - return this.getPage().locator('[data-qa-setting-id="Accounts_Default_User_Preferences_notificationsSoundVolume"]'); - } - - public adminCheckBox(checkBox: string): Locator { - return this.getPage().locator(`//label[text()="${checkBox}"]/preceding-sibling::label/i`); - } - - public inputPermissionsSearch(): Locator { - return this.getPage().locator('.main-content input[placeholder="Search"]'); - } - - public getCheckboxPermission(label: string, column = 6): Locator { - return this.getPage().locator(`tr td:has-text("${label}") ~ td:nth-child(${column})`).locator('label').first(); - } - - public userInfoActions(): Locator { - return this.getPage().locator('[data-qa-id="UserInfoActions"]'); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/Agents.ts b/apps/meteor/tests/e2e/utils/pageobjects/Agents.ts deleted file mode 100644 index 89733b6419a1..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/Agents.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Locator, expect } from '@playwright/test'; - -import BasePage from './BasePage'; - -export default class Agents extends BasePage { - public agentsLink(): Locator { - return this.getPage().locator('a[href="omnichannel/agents"]'); - } - - public textAgentsTitle(): Locator { - return this.getPage().locator('h2 >> text="Agents"'); - } - - public inputAgentsUserName(): Locator { - return this.getPage().locator('input').first(); - } - - public userOption(): Locator { - return this.getPage().locator('.rcx-option >> text="Rocket.Cat"'); - } - - public btnAddAgents(): Locator { - return this.getPage().locator('button.rcx-button--primary.rcx-button >> text="Add"'); - } - - public agentAdded(): Locator { - return this.getPage().locator('[data-qa="GenericTableAgentInfoBody"] .rcx-table__row--action .rcx-table__cell:first-child'); - } - - public agentListStatus(): Locator { - return this.getPage().locator('[data-qa="GenericTableAgentInfoBody"] .rcx-table__row--action .rcx-table__cell:nth-child(4)'); - } - - public userInfoTab(): Locator { - return this.getPage().locator('h3 div'); - } - - public agentInfo(): Locator { - return this.getPage().locator('[data-qa="AgentInfoUserInfoUserName"]'); - } - - public agentInfoUserInfoLabel(): Locator { - return this.getPage().locator('[data-qa="AgentInfoUserInfoLabel"]'); - } - - public btnClose(): Locator { - return this.getPage().locator('[data-qa="VerticalBarActionClose"]'); - } - - public userAvatar(): Locator { - return this.getPage().locator('[data-qa="AgentUserInfoAvatar"]'); - } - - public btnEdit(): Locator { - return this.getPage().locator('[data-qa="AgentInfoAction-Edit"]'); - } - - public btnRemove(): Locator { - return this.getPage().locator('[data-qa="AgentInfoAction-Remove"]'); - } - - public availabilityOption(availability: string): Locator { - return this.getPage().locator(`div.rcx-options[role="listbox"] div.rcx-box ol[role="listbox"] li[value="${availability}"]`); - } - - public btnTableRemove(): Locator { - return this.getPage().locator( - '[data-qa="GenericTableAgentInfoBody"] .rcx-table__row--action .rcx-table__cell:nth-child(5) [title="Remove"]', - ); - } - - public agentStatus(): Locator { - return this.getPage().locator('[data-qa="AgentEditTextInput-Status"]'); - } - - public btnAgentSave(): Locator { - return this.getPage().locator('[data-qa="AgentEditButtonSave"]'); - } - - public getAgentInputs(id: string): Locator { - return this.getPage().locator(`[data-qa="AgentEditTextInput-${id}"]`); - } - - public async doAddAgent(): Promise { - await this.textAgentsTitle().waitFor(); - await this.inputAgentsUserName().type('Rocket.Cat', { delay: 50 }); - - await this.userOption().click(); - await this.btnAddAgents().click(); - } - - public async getListOfExpectedInputs(): Promise { - const inputs = ['Name', 'Username', 'Email', 'Departaments', 'Status'].map((id) => this.getAgentInputs(id)); - await Promise.all(inputs.map((input) => expect(input).toBeVisible())); - await this.btnClose().click(); - } - - public async doChangeUserStatus(availability: string): Promise { - await this.agentAdded().click(); - await this.btnEdit().click(); - await this.agentStatus().click(); - await this.availabilityOption(availability).click(); - await this.btnAgentSave().click(); - } - - public async doRemoveAgent(): Promise { - await this.agentAdded().click(); - await this.btnRemove().click(); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/BasePage.ts b/apps/meteor/tests/e2e/utils/pageobjects/BasePage.ts deleted file mode 100644 index 79ea81fb81ac..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/BasePage.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Page, Locator } from '@playwright/test'; - -class BasePage { - private page: Page; - - constructor(page: Page) { - this.page = page; - } - - public getPage(): Page { - return this.page; - } - - public async goto(path: string): Promise { - await this.getPage().goto(path); - } - - public async waitForSelector(selector: string): Promise { - await this.getPage().waitForSelector(selector); - } - - public async keyboardPress(key: string): Promise { - await this.getPage().keyboard.press(key); - } - - public modal(): Locator { - return this.getPage().locator('#modal-root'); - } - - public btnModalCancel(): Locator { - return this.getPage().locator( - '#modal-root div dialog div.rcx-modal__inner div.rcx-modal__footer div div button.rcx-button--ghost.rcx-button', - ); - } - - public btnModalRemove(): Locator { - return this.getPage().locator( - '#modal-root div dialog div.rcx-modal__inner div.rcx-modal__footer div div button.rcx-button--primary-danger.rcx-button', - ); - } -} -export default BasePage; diff --git a/apps/meteor/tests/e2e/utils/pageobjects/ChannelCreation.ts b/apps/meteor/tests/e2e/utils/pageobjects/ChannelCreation.ts deleted file mode 100644 index 4928918c8f01..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/ChannelCreation.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Locator, expect } from '@playwright/test'; - -import BasePage from './BasePage'; -import { ENTER } from '../mocks/keyboardKeyMock'; - -export default class ChannelCreation extends BasePage { - private buttonCreate(): Locator { - return this.getPage().locator('[data-qa="sidebar-create"]'); - } - - private inputChannelName(): Locator { - return this.getPage().locator('[placeholder="Channel Name"]'); - } - - private inputChannelDescription(): Locator { - return this.getPage().locator('[placeholder="What is this channel about?"]'); - } - - private buttonCreateChannel(): Locator { - return this.getPage().locator('//ul[@class="rc-popover__list"]//li[@class="rcx-option"][1]'); - } - - private channelName(): Locator { - return this.getPage().locator('//header//div//div//div//div[2]'); - } - - private buttonConfirmCreation(): Locator { - return this.getPage().locator('//button[contains(text(), "Create" )]'); - } - - private privateChannel(): Locator { - return this.getPage().locator('//label[contains(text(),"Private")]/../following-sibling::label/i'); - } - - private searchChannel(): Locator { - return this.getPage().locator('[data-qa="sidebar-search"]'); - } - - private searchChannelInput(): Locator { - return this.getPage().locator('[data-qa="sidebar-search-input"]'); - } - - private textArea(): Locator { - return this.getPage().locator('.rc-message-box__textarea'); - } - - private lastMessage(): Locator { - return this.getPage().locator('.message:last-child .body'); - } - - public async createChannel(name: string, isPrivate: boolean): Promise { - await this.buttonCreate().click(); - await this.buttonCreateChannel().click(); - await this.inputChannelName().type(name); - await this.inputChannelDescription().type('any_description'); - if (!isPrivate) { - await this.privateChannel().click(); - } - await this.buttonConfirmCreation().click(); - - await expect(this.channelName()).toHaveText(name); - } - - public async sendMessage(targetUser: string, message: string): Promise { - await this.searchChannel().click(); - await this.searchChannelInput().type(targetUser, { delay: 200 }); - await this.keyboardPress(ENTER); - - await this.textArea().type(message); - await this.keyboardPress(ENTER); - - await expect(this.lastMessage()).toBeVisible(); - await expect(this.lastMessage()).toHaveText(message); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/Discussion.ts b/apps/meteor/tests/e2e/utils/pageobjects/Discussion.ts deleted file mode 100644 index afe9a22ab328..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/Discussion.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Locator, expect } from '@playwright/test'; - -import BasePage from './BasePage'; - -export default class Discussion extends BasePage { - public startDiscussionContextItem(): Locator { - return this.getPage().locator('[data-qa-id="start-discussion"][data-qa-type="message-action"]'); - } - - public createDiscussionBtn(): Locator { - return this.getPage().locator('.rcx-option__content >> text="Discussion"'); - } - - public channelName(): Locator { - return this.getPage().locator('.rcx-input-box--undecorated.rcx-input-box').first(); - } - - public discussionName(): Locator { - return this.getPage().locator('[placeholder="A meaningful name for the discussion room"]'); - } - - public discussionMessage(): Locator { - return this.getPage().locator('textarea.rcx-input-box'); - } - - public buttonCreateDiscussion(): Locator { - return this.getPage().locator('button.rcx-button--primary.rcx-button >> text="Create"'); - } - - public discussionCreated(discussionName: string): Locator { - return this.getPage().locator(`[data-qa="sidebar-item-title"] >> text='${discussionName}'`); - } - - async createDiscussion(channelName: string, discussionName: string, message: string): Promise { - await this.createDiscussionBtn().click(); - await this.channelName().type(channelName); - await this.getPage().keyboard.press('Enter'); - - await this.discussionName().type(discussionName); - await this.discussionMessage().type(message); - - await this.buttonCreateDiscussion().click(); - - await expect(this.discussionCreated(discussionName)).toBeVisible(); - } - - async createDiscussionInContext(message: string): Promise { - await this.startDiscussionContextItem().waitFor(); - await this.getPage().pause(); - await this.startDiscussionContextItem().click(); - await this.buttonCreateDiscussion().click(); - await expect(this.discussionCreated(message)).toBeVisible(); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/FlexTab.ts b/apps/meteor/tests/e2e/utils/pageobjects/FlexTab.ts deleted file mode 100644 index 2c2a8f6ac128..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/FlexTab.ts +++ /dev/null @@ -1,407 +0,0 @@ -import { expect, Locator } from '@playwright/test'; - -import BasePage from './BasePage'; -import Global from './Global'; - -export default class FlexTab extends BasePage { - private global = new Global(this.getPage()); - - public mainSideBar(): Locator { - return this.getPage().locator('//main//aside'); - } - - public mainSideBarBack(): Locator { - return this.getPage().locator('(//main//aside/h3//button)[1]'); - } - - public mainSideBarClose(): Locator { - return this.getPage().locator('//main//aside/h3//i[contains(@class, "rcx-icon--name-cross")]/..'); - } - - public headerMoreActions(): Locator { - return this.getPage().locator('//main/header//*[contains(@class, "rcx-icon--name-kebab")]/..'); - } - - public messageInput(): Locator { - return this.getPage().locator('.rcx-vertical-bar .js-input-message'); - } - - public channelTab(): Locator { - return this.getPage().locator('(//main//*[contains(@class, "rcx-icon--name-info-circled")])[1]/..'); - } - - public channelSettings(): Locator { - return this.getPage().locator( - '//aside/h3/div/i[contains(@class,"rcx-icon--name-info-circled") and contains(@class,"rcx-icon--name-info-circled")]', - ); - } - - public editNameBtn(): Locator { - return this.getPage().locator('//aside//button[contains(text(), "Edit")]'); - } - - public editUnreadAlertBtn(): Locator { - return this.getPage().locator('[data-edit="unreadAlert"]'); - } - - public editNameTextInput(): Locator { - return this.getPage().locator('//aside//label[contains(text(), "Name")]/..//input'); - } - - public editTopicTextInput(): Locator { - return this.getPage().locator('//main//aside//label[contains(text(), "Topic")]/..//textarea'); - } - - public editAnnouncementTextInput(): Locator { - return this.getPage().locator('//main//aside//label[contains(text(), "Announcement")]/..//textarea'); - } - - public editDescriptionTextInput(): Locator { - return this.getPage().locator('//main//aside//label[contains(text(), "Description")]/..//textarea'); - } - - public editNameSave(): Locator { - return this.getPage().locator('//aside//button[contains(text(), "Save")]'); - } - - public membersTab(): Locator { - return this.getPage().locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-members'); - } - - public membersTabContent(): Locator { - return this.getPage().locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-members'); - } - - public setOwnerBtn(): Locator { - return this.getPage().locator('//main//aside//button[contains(text(), "Set as owner")]'); - } - - public setModeratorBtn(): Locator { - return this.getPage().locator('[value="changeModerator"]'); - } - - public muteUserBtn(): Locator { - return this.getPage().locator('[value="muteUser"]'); - } - - public avatarImage(): Locator { - return this.getPage().locator('(//aside[contains(@class, "rcx-vertical-bar")]//*[contains(@class, "avatar")])[1]'); - } - - public memberRealName(): Locator { - return this.getPage().locator('[data-qa="UserInfoUserName"]'); - } - - public searchTab(): Locator { - return this.getPage().locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-magnifier'); - } - - public searchTabContent(): Locator { - return this.getPage().locator('.rocket-search-result'); - } - - public messageSearchBar(): Locator { - return this.getPage().locator('#message-search'); - } - - public searchResult(): Locator { - return this.getPage().locator('.new-day'); - } - - public notificationsTab(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Notifications Preferences")]'); - } - - public notificationsSettings(): Locator { - return this.getPage().locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-bell'); - } - - public filesTab(): Locator { - return this.getPage().locator('.rcx-room-header .rcx-button-group__item:not(.hidden) .rcx-icon--name-clip'); - } - - public filesTabContent(): Locator { - return this.getPage().locator('aside > h3 > div > i.rcx-icon--name-attachment'); - } - - public fileDownload(): Locator { - return this.getPage().locator('.uploaded-files-list ul:first-child .file-download'); - } - - public fileName(): Locator { - return this.getPage().locator('.uploaded-files-list ul:first-child .room-file-item'); - } - - public mentionsTab(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Mentions")]'); - } - - public mentionsTabContent(): Locator { - return this.getPage().locator('aside > h3 > div > i.rcx-icon--name-at'); - } - - public starredTab(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Starred Messages")]'); - } - - public starredTabContent(): Locator { - return this.getPage().locator('aside > h3 > div > i.rcx-icon--name-star'); - } - - public pinnedTab(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Pinned Messages")]'); - } - - public pinnedTabContent(): Locator { - return this.getPage().locator('aside > h3 > div > i.rcx-icon--name-pin'); - } - - public firstSetting(): Locator { - return this.getPage().locator('//aside//i[contains(@class, "rcx-icon--name-hashtag")]/../div'); - } - - public secondSetting(topic: string): Locator { - return this.getPage().locator(`//header//*[contains(text(), "${topic}")]`); - } - - public thirdSetting(): Locator { - return this.getPage().locator('[data-qa="AnnouncementAnnoucementComponent"] div:nth-child(1)'); - } - - public fourthSetting(): Locator { - return this.getPage().locator('//main//aside//div[contains(text(), "Description")]//following-sibling::div'); - } - - public usersAddUserTab(): Locator { - return this.getPage().locator('//button[text()="New"]'); - } - - public usersAddUserTabClose(): Locator { - return this.getPage().locator('//div[text()="Add User"]//button'); - } - - public usersButtonCancel(): Locator { - return this.getPage().locator('//button[text()="Cancel"]'); - } - - public usersButtonSave(): Locator { - return this.getPage().locator('//button[text()="Save"]'); - } - - public usersAddUserName(): Locator { - return this.getPage().locator('//label[text()="Name"]/following-sibling::span//input'); - } - - public usersAddUserUsername(): Locator { - return this.getPage().locator('//label[text()="Username"]/following-sibling::span//input'); - } - - public usersAddUserEmail(): Locator { - return this.getPage().locator('//label[text()="Email"]/following-sibling::span//input').first(); - } - - public usersAddUserRoleList(): Locator { - return this.getPage().locator('//label[text()="Roles"]/following-sibling::span//input'); - } - - public fileDescription(): Locator { - return this.getPage().locator( - '//li[@data-username="rocketchat.internal.admin.test"][last()]//div[@class="js-block-wrapper"]/following-sibling::div//div//p', - ); - } - - public usersAddUserPassword(): Locator { - return this.getPage().locator('//label[text()="Password"]/following-sibling::span//input'); - } - - public usersAddUserVerifiedCheckbox(): Locator { - return this.getPage().locator('//label[text()="Email"]/following-sibling::span//input/following-sibling::i'); - } - - public usersAddUserChangePasswordCheckbox(): Locator { - return this.getPage().locator('//div[text()="Require password change"]/following-sibling::label//input'); - } - - public usersAddUserDefaultChannelCheckbox(): Locator { - return this.getPage().locator('//div[text()="Join default channels"]/following-sibling::label//input'); - } - - public usersAddUserWelcomeEmailCheckbox(): Locator { - return this.getPage().locator('//div[text()="Send welcome email"]/following-sibling::label//input'); - } - - public usersAddUserRandomPassword(): Locator { - return this.getPage().locator('//div[text()="Set random password and send by email"]/following-sibling::label//input'); - } - - public closeThreadMessage(): Locator { - return this.getPage().locator( - '//html//body//div[1]//div//div[3]//div[1]//main//div//aside//div[2]//div//div//h3//div//div[2]//button[2]', - ); - } - - public getUserEl(username: string): Locator { - return this.getPage().locator(`[data-qa="MemberItem-${username}"]`); - } - - public addUserTable(): Locator { - return this.getPage().locator('//div[text()="Add User"]'); - } - - public addUserButton(): Locator { - return this.getPage().locator('//button[contains(text(), "Add")]'); - } - - public addUserButtonAfterChoose(): Locator { - return this.getPage().locator('//button[contains(text(), "Add users")]'); - } - - public chooseUserSearch(): Locator { - return this.getPage().locator('//label[contains(text(), "Choose users")]/..//input'); - } - - public chooseUserOptions(): Locator { - return this.getPage().locator('(//div[@role="option"]//ol/li)[1]'); - } - - public userMoreActions(): Locator { - return this.getPage().locator('[data-qa="UserUserInfo-menu"]'); - } - - public async setUserOwner(user: string): Promise { - await this.enterUserView(user); - await this.setOwnerBtn().waitFor(); - await this.setOwnerBtn().click(); - } - - public async setUserModerator(user: string): Promise { - await this.enterUserView(user); - await this.userMoreActions().click(); - await this.setModeratorBtn().waitFor(); - await this.setModeratorBtn().click(); - } - - public async muteUser(user: string): Promise { - await this.enterUserView(user); - await this.userMoreActions().click(); - await this.muteUserBtn().waitFor(); - await this.muteUserBtn().click(); - await this.global.confirmPopup(); - await this.mainSideBarBack().click(); - } - - public async enterUserView(user: string): Promise { - const userEl = this.getUserEl(user); - await userEl.waitFor(); - await userEl.click(); - } - - public async addPeopleToChannel(user: string): Promise { - await this.addUserButton().click(); - await this.chooseUserSearch().type(user); - await this.getPage().waitForTimeout(3000); - await this.chooseUserOptions().click(); - await this.addUserButtonAfterChoose().click(); - } - - public async operateFlexTab(desiredTab: string, desiredState: boolean): Promise { - // desiredState true=open false=closed - const locator: { [K: string]: Locator } = { - channelSettings: this.channelSettings(), - messageSearchBar: this.messageSearchBar(), - avatarImage: this.avatarImage(), - notificationsSettings: this.notificationsSettings(), - filesTabContent: this.filesTabContent(), - mentionsTabContent: this.mentionsTabContent(), - starredTabContent: this.starredTabContent(), - pinnedTabContent: this.pinnedTabContent(), - channelTab: this.channelTab(), - searchTab: this.searchTab(), - membersTab: this.membersTab(), - notificationsTab: this.notificationsTab(), - filesTab: this.filesTab(), - mentionsTab: this.mentionsTab(), - starredTab: this.starredTab(), - pinnedTab: this.pinnedTab(), - }; - - const operate = async (tab: string, panel: string, more: boolean): Promise => { - // this[panel].should(!desiredState ? 'be.visible' : 'not.exist'); - if (!desiredState) { - await expect(locator[panel]).toBeVisible(); - } else { - await expect(locator[panel]).not.toBeVisible(); - } - - if (more) { - await this.headerMoreActions().click(); - } - - await locator[tab].click(); - - // The button "more" keeps the focus when popover is closed from a click - // on an item, need to click again to change the status to unselected and - // allow the next click to open the popover again - if (more) { - await this.headerMoreActions().click(); - } - - if (desiredState) { - await expect(locator[panel]).toBeVisible(); - } else { - await expect(locator[panel]).not.toBeVisible(); - } - }; - - const tabs: { [K: string]: Function } = { - info: async (): Promise => { - await operate('channelTab', 'channelSettings', false); - }, - - search: async (): Promise => { - await operate('searchTab', 'messageSearchBar', false); - }, - - members: async (): Promise => { - await operate('membersTab', 'avatarImage', false); - }, - - notifications: async (): Promise => { - await operate('notificationsTab', 'notificationsSettings', true); - }, - - files: async (): Promise => { - await operate('filesTab', 'filesTabContent', false); - }, - - mentions: async (): Promise => { - await operate('mentionsTab', 'mentionsTabContent', true); - }, - - starred: async (): Promise => { - await operate('starredTab', 'starredTabContent', true); - }, - - pinned: async (): Promise => { - await operate('pinnedTab', 'pinnedTabContent', true); - }, - }; - - const callFunctionTabs = async (name: string): Promise => { - return tabs[name](); - }; - - await callFunctionTabs(desiredTab); - } - - public flexTabViewThreadMessage(): Locator { - return this.getPage().locator( - 'div.thread-list.js-scroll-thread ul.thread [data-qa-type="message"]:last-child div.message-body-wrapper [data-qa-type="message-body"]', - ); - } - - public async doAddRole(role: string): Promise { - await this.usersAddUserRoleList().click(); - await this.getPage().locator(`li[value=${role}]`).click(); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/Global.ts b/apps/meteor/tests/e2e/utils/pageobjects/Global.ts deleted file mode 100644 index 4874f40af229..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/Global.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Locator } from '@playwright/test'; - -import BasePage from './BasePage'; - -export default class Global extends BasePage { - public modalConfirm(): Locator { - return this.getPage().locator('.rcx-modal .rcx-button--primary-danger'); - } - - public modalFilePreview(): Locator { - return this.getPage().locator('.rc-modal .upload-preview-file'); - } - - public getToastBar(): Locator { - return this.getPage().locator('.rcx-toastbar'); - } - - public getToastBarError(): Locator { - return this.getPage().locator('.rcx-toastbar.rcx-toastbar--error'); - } - - public getToastBarSuccess(): Locator { - return this.getPage().locator('.rcx-toastbar.rcx-toastbar--success'); - } - - public flexNav(): Locator { - return this.getPage().locator('.flex-nav'); - } - - public async confirmPopup(): Promise { - await this.modalConfirm().waitFor(); - await this.modalConfirm().click(); - } - - public async dismissToastBar(): Promise { - await this.getToastBar().locator('button').click(); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/LoginPage.ts b/apps/meteor/tests/e2e/utils/pageobjects/LoginPage.ts deleted file mode 100644 index e62277ccd5fb..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/LoginPage.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Locator, expect } from '@playwright/test'; - -import { ILogin, IRegister } from '../interfaces/Login'; -import BasePage from './BasePage'; -import { HOME_SELECTOR, REGISTER_STEP2_BUTTON } from '../mocks/waitSelectorsMock'; - -export default class LoginPage extends BasePage { - private registerButton(): Locator { - return this.getPage().locator('button.register'); - } - - private forgotPasswordButton(): Locator { - return this.getPage().locator('.forgot-password'); - } - - public submitButton(): Locator { - return this.getPage().locator('.login'); - } - - public registerNextButton(): Locator { - return this.getPage().locator('button[data-loading-text=" Please_wait ..."]'); - } - - public emailOrUsernameField(): Locator { - return this.getPage().locator('[name=emailOrUsername]'); - } - - public nameField(): Locator { - return this.getPage().locator('[name=name]'); - } - - public emailField(): Locator { - return this.getPage().locator('[name=email]'); - } - - public passwordField(): Locator { - return this.getPage().locator('[name=pass]'); - } - - public confirmPasswordField(): Locator { - return this.getPage().locator('[name=confirm-pass]'); - } - - public nameInvalidText(): Locator { - return this.getPage().locator('[name=name]~.input-error'); - } - - public emailInvalidText(): Locator { - return this.getPage().locator('[name=email]~.input-error'); - } - - public passwordInvalidText(): Locator { - return this.getPage().locator('[name=pass]~.input-error'); - } - - public confirmPasswordInvalidText(): Locator { - return this.getPage().locator('[name=confirm-pass]~.input-error'); - } - - public getSideBarAvatarButton(): Locator { - return this.getPage().locator('[data-qa="sidebar-avatar-button"]'); - } - - public async gotToRegister(): Promise { - await this.registerButton().click(); - } - - public async gotToForgotPassword(): Promise { - await this.forgotPasswordButton().click(); - } - - public async registerNewUser({ name, email, password }: IRegister): Promise { - await this.nameField().type(name); - await this.emailField().type(email); - await this.passwordField().type(password); - await this.confirmPasswordField().type(password); - await this.submit(); - - await this.waitForSelector(REGISTER_STEP2_BUTTON); - await this.registerNextButton().click(); - await this.waitForSelector(HOME_SELECTOR); - } - - public async login({ email, password }: ILogin): Promise { - await this.emailOrUsernameField().type(email); - await this.passwordField().type(password); - await this.submitButton().click(); - } - - public async submit(): Promise { - await this.submitButton().click(); - } - - public async registerFail(): Promise { - await this.gotToRegister(); - await this.submit(); - - await expect(this.nameInvalidText()).toBeVisible(); - await expect(this.emailInvalidText()).toBeVisible(); - await expect(this.passwordInvalidText()).toBeVisible(); - } - - public async registerFailWithDifferentPassword({ name, email, password }: IRegister, invalidPassword: string): Promise { - await this.gotToRegister(); - await this.passwordField().type(password); - await this.emailField().type(email); - await this.nameField().type(name); - await this.confirmPasswordField().type(invalidPassword); - - await this.submit(); - await expect(this.confirmPasswordInvalidText()).toBeVisible(); - await expect(this.confirmPasswordInvalidText()).toHaveText('The password confirmation does not match password'); - } - - public async logout(): Promise { - await this.getSideBarAvatarButton().click(); - await this.getPage().locator('li.rcx-option >> text="Logout"').click(); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/MainContent.ts b/apps/meteor/tests/e2e/utils/pageobjects/MainContent.ts deleted file mode 100644 index d5af8fdb4653..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/MainContent.ts +++ /dev/null @@ -1,345 +0,0 @@ -import fs from 'fs'; - -import { expect, Locator } from '@playwright/test'; - -import BasePage from './BasePage'; - -export default class MainContent extends BasePage { - public mainContent(): Locator { - return this.getPage().locator('.main-content'); - } - - public emptyFavoriteStar(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-room-header")]//*[contains(@class, "rcx-icon--name-star")]'); - } - - public favoriteStar(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-room-header")]//*[contains(@class, "rcx-icon--name-star-filled")]'); - } - - public channelTitle(title: string): Locator { - return this.getPage().locator('.rcx-room-header', { hasText: title }); - } - - public messageInput(): Locator { - return this.getPage().locator('[name="msg"]'); - } - - public sendBtn(): Locator { - return this.getPage().locator('.rc-message-box__icon.js-send'); - } - - public messageBoxActions(): Locator { - return this.getPage().locator('(//*[contains(@class, "rc-message-box__icon")])[1]'); - } - - public recordBtn(): Locator { - return this.getPage().locator('[data-qa-id="audio-record"]'); - } - - public emojiBtn(): Locator { - return this.getPage().locator('.rc-message-box__icon.emoji-picker-icon'); - } - - public messagePopUp(): Locator { - return this.getPage().locator('.message-popup'); - } - - public messagePopUpTitle(): Locator { - return this.getPage().locator('.message-popup-title'); - } - - public messagePopUpItems(): Locator { - return this.getPage().locator('.message-popup-items'); - } - - public messagePopUpFirstItem(): Locator { - return this.getPage().locator('.popup-item.selected'); - } - - public lastMessageUser(): Locator { - return this.getPage().locator('.message:last-child div:nth-child(2) button'); - } - - public lastMessageFileName(): Locator { - return this.getPage().locator('[data-qa-type="message"]:last-child div:nth-child(3) div:nth-child(2) div a:nth-child(1)'); - } - - public lastMessage(): Locator { - return this.getPage().locator('.messages-box [data-qa-type="message"]').last(); - } - - public lastMessageRoleAdded(): Locator { - return this.getPage().locator('.message:last-child.subscription-role-added .body'); - } - - public lastMessageUserTag(): Locator { - return this.getPage().locator('.message:last-child .role-tag'); - } - - public lastMessageForMessageTest(): Locator { - return this.getPage().locator('[data-qa-type="message"]:last-child div.message-body-wrapper div:nth-child(2)'); - } - - public messageOptionsBtns(): Locator { - return this.getPage().locator('.message:last-child .message-actions'); - } - - public messageReply(): Locator { - return this.getPage().locator('[data-qa-id="reply-in-thread"]'); - } - - public messageEdit(): Locator { - return this.getPage().locator('[data-qa-id="edit-message"]'); - } - - public messageDelete(): Locator { - return this.getPage().locator('[data-qa-id="delete-message"]'); - } - - public messagePermalink(): Locator { - return this.getPage().locator('[data-qa-id="permalink"]'); - } - - public messageCopy(): Locator { - return this.getPage().locator('[data-qa-id="copy"]'); - } - - public messageQuote(): Locator { - return this.getPage().locator('[data-qa-id="quote-message"]'); - } - - public messageStar(): Locator { - return this.getPage().locator('[data-qa-id="star-message"]'); - } - - public messageUnread(): Locator { - return this.getPage().locator('[data-id="mark-message-as-unread"][data-type="message-action"]'); - } - - public emojiPickerMainScreen(): Locator { - return this.getPage().locator('.emoji-picker'); - } - - public emojiPickerPeopleIcon(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-people")]'); - } - - public emojiPickerNatureIcon(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-nature")]'); - } - - public emojiPickerFoodIcon(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-food")]'); - } - - public emojiPickerActivityIcon(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-activity")]'); - } - - public emojiPickerTravelIcon(): Locator { - return this.getPage().locator('.emoji-picker .icon-travel'); - } - - public emojiPickerObjectsIcon(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-objects")]'); - } - - public emojiPickerSymbolsIcon(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-symbols")]'); - } - - public emojiPickerFlagsIcon(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-flags")]'); - } - - public emojiPickerCustomIcon(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-rocket")]'); - } - - public emojiPickerFilter(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "js-emojipicker-search")]'); - } - - public emojiPickerChangeTone(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "change-tone")]'); - } - - public emojiGrinning(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "emoji-grinning")]'); - } - - public emojiSmile(): Locator { - return this.getPage().locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "emoji-smile")]'); - } - - public modalTitle(): Locator { - return this.getPage().locator('#modal-root .rcx-modal__title'); - } - - public modalCancelButton(): Locator { - return this.getPage().locator('#modal-root .rcx-button-group--align-end .rcx-button--ghost'); - } - - public modalDeleteMessageButton(): Locator { - return this.getPage().locator('#modal-root .rcx-button-group--align-end .rcx-button--primary-danger'); - } - - public buttonSend(): Locator { - return this.getPage().locator('#modal-root .rcx-button-group--align-end .rcx-button--primary'); - } - - public modalFilePreview(): Locator { - return this.getPage().locator( - '//div[@id="modal-root"]//header//following-sibling::div[1]//div//div//img | //div[@id="modal-root"]//header//following-sibling::div[1]//div//div//div//i', - ); - } - - public fileName(): Locator { - return this.getPage().locator('//div[@id="modal-root"]//fieldset//div[1]//label'); - } - - public fileDescription(): Locator { - return this.getPage().locator('//div[@id="modal-root"]//fieldset//div[2]//label'); - } - - public async waitForLastMessageEqualsHtml(text: string): Promise { - await expect(this.getPage().locator('(//*[contains(@class, "message") and contains(@class, "body")])[last()]')).toContainText(text); - } - - public async waitForLastMessageEqualsText(text: string): Promise { - await expect(this.getPage().locator('(//*[contains(@class, "message") and contains(@class, "body")])[last()]')).toContainText(text); - } - - public async sendMessage(text: string): Promise { - await this.setTextToInput(text); - await this.keyboardPress('Enter'); - } - - public async addTextToInput(text: any): Promise { - await this.messageInput().type(text); - } - - public async setTextToInput(text: string, options: { delay?: number } = {}): Promise { - await this.messageInput().click({ clickCount: 3 }); - await this.getPage().keyboard.press('Backspace'); - await this.messageInput().type(text, { delay: options.delay ?? 0 }); - } - - public async dragAndDropFile(): Promise { - const contract = await fs.promises.readFile('./tests/e2e/utils/fixtures/any_file.txt', 'utf-8'); - - const dataTransfer = await this.getPage().evaluateHandle((contract) => { - const data = new DataTransfer(); - const file = new File([`${contract}`], 'any_file.txt', { - type: 'text/plain', - }); - data.items.add(file); - return data; - }, contract); - - await this.getPage().dispatchEvent( - 'div.dropzone-overlay.dropzone-overlay--enabled.background-transparent-darkest.color-content-background-color', - 'drop', - { dataTransfer }, - ); - } - - public async sendFileClick(): Promise { - await this.buttonSend().click(); - } - - public descriptionInput(): Locator { - return this.getPage().locator('//div[@id="modal-root"]//fieldset//div[2]//span//input'); - } - - public fileNameInput(): Locator { - return this.getPage().locator('//div[@id="modal-root"]//fieldset//div[1]//span//input'); - } - - public async setFileName(): Promise { - await this.fileNameInput().fill('any_file1.txt'); - } - - public async setDescription(): Promise { - await this.descriptionInput().type('any_description'); - } - - public getFileDescription(): Locator { - return this.getPage().locator('[data-qa-type="message"]:last-child div:nth-child(3) div:nth-child(2) div p'); - } - - public async selectAction(action: string): Promise { - switch (action) { - case 'edit': - await this.messageEdit().click(); - await this.messageInput().fill('this message was edited'); - await this.keyboardPress('Enter'); - await expect(this.lastMessageForMessageTest()).toHaveText('this message was edited'); - break; - case 'reply': - this.messageReply().click(); - break; - case 'delete': - await this.messageDelete().click(); - await this.acceptDeleteMessage(); - await expect(this.lastMessageForMessageTest()).not.toHaveText('Message for Message Delete Tests'); - break; - case 'permalink': - await this.messagePermalink().click(); - break; - case 'copy': - await this.messageCopy().click(); - break; - case 'quote': - await this.messageQuote().click(); - await this.messageInput().type('this is a quote message'); - await this.keyboardPress('Enter'); - break; - case 'star': - await this.messageStar().click(); - await expect(this.getPage().locator('div.rcx-toastbar:has-text("Message has been starred")')).toBeVisible(); - break; - case 'unread': - await this.messageUnread().click(); - break; - case 'reaction': - await this.messageReply().click(); - await this.emojiPickerPeopleIcon().click(); - await this.emojiGrinning().click(); - break; - } - } - - public async openMessageActionMenu(): Promise { - await this.getPage().locator('.messages-box [data-qa-type="message"]:last-child').hover(); - await this.getPage().locator('[data-qa-type="message"]:last-child div.message-actions__menu').waitFor(); - await this.getPage().locator('[data-qa-type="message"]:last-child div.message-actions__menu').click(); - } - - public async openMoreActionMenu(): Promise { - await this.getPage().locator('.rc-message-box [data-qa-id="menu-more-actions"]').click(); - await this.getPage().waitForSelector('.rc-popover__content'); - } - - public async acceptDeleteMessage(): Promise { - await this.modalDeleteMessageButton().click(); - } - - public waitForLastMessageTextAttachmentEqualsText(): Locator { - return this.getPage().locator('[data-qa-type="message"]:last-child .rcx-attachment__details .rcx-box--with-inline-elements'); - } - - public userCard(): Locator { - return this.getPage().locator('[data-qa="UserCard"]'); - } - - public viewUserProfile(): Locator { - return this.getPage().locator('[data-qa="UserCard"] a'); - } - - public async doReload(): Promise { - await this.getPage().reload({ waitUntil: 'load' }); - await this.getPage().waitForSelector('.messages-box'); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/PreferencesMainContent.ts b/apps/meteor/tests/e2e/utils/pageobjects/PreferencesMainContent.ts deleted file mode 100644 index 5d8c95441ccf..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/PreferencesMainContent.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { expect, Locator } from '@playwright/test'; - -import BasePage from './BasePage'; - -export default class PreferencesMainContent extends BasePage { - public realNameTextInput(): Locator { - return this.getPage().locator('//label[contains(text(), "Name")]/..//input'); - } - - public userNameTextInput(): Locator { - return this.getPage().locator('//label[contains(text(), "Username")]/..//input'); - } - - public emailTextInput(): Locator { - return this.getPage().locator('//label[contains(text(), "Email")]/..//input'); - } - - public passwordTextInput(): Locator { - return this.getPage().locator('//label[contains(text(), "Password")]/..//input'); - } - - public avatarFileInput(): Locator { - return this.getPage().locator('.avatar-file-input'); - } - - public useUploadedAvatar(): Locator { - return this.getPage().locator('.avatar-suggestion-item:nth-of-type(2) .select-service'); - } - - public submitBtn(): Locator { - return this.getPage().locator('[data-qa="AccountProfilePageSaveButton"]'); - } - - public async changeUsername(userName: string): Promise { - await this.userNameTextInput().fill(userName); - } - - public async changeRealName(realName: string): Promise { - await this.realNameTextInput().fill(realName); - } - - public async saveChanges(): Promise { - await expect(this.submitBtn()).toBeEnabled(); - await this.submitBtn().click(); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/SetupWizard.ts b/apps/meteor/tests/e2e/utils/pageobjects/SetupWizard.ts deleted file mode 100644 index f32d5a3c9913..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/SetupWizard.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { expect, Locator } from '@playwright/test'; - -import BasePage from './BasePage'; -import { reason, INVALID_EMAIL_WITHOUT_MAIL_PROVIDER } from '../mocks/userAndPasswordMock'; -import { IRegister } from '../interfaces/Login'; -import { BACKSPACE } from '../mocks/keyboardKeyMock'; - -export default class SetupWizard extends BasePage { - private nextStep(): Locator { - return this.getPage().locator('//button[contains(text(), "Next")]'); - } - - private fullName(): Locator { - return this.getPage().locator('[name="fullname"]'); - } - - private userName(): Locator { - return this.getPage().locator('[name="username"]'); - } - - private companyEmail(): Locator { - return this.getPage().locator('[name="companyEmail"]'); - } - - private password(): Locator { - return this.getPage().locator('[name="password"]'); - } - - public goToWorkspace(): Locator { - return this.getPage().locator('//button[contains(text(), "Confirm")]'); - } - - private organizationType(): Locator { - return this.getPage().locator('[name="organizationType"]'); - } - - private organizationTypeSelect(): Locator { - return this.getPage().locator('.rcx-options .rcx-option:first-child'); - } - - private organizationName(): Locator { - return this.getPage().locator('[name="organizationName"]'); - } - - private industry(): Locator { - return this.getPage().locator('[name="organizationIndustry"]'); - } - - private industrySelect(): Locator { - return this.getPage().locator('.rcx-options .rcx-option:first-child'); - } - - private size(): Locator { - return this.getPage().locator('[name="organizationSize"]'); - } - - private sizeSelect(): Locator { - return this.getPage().locator('.rcx-options .rcx-option:first-child'); - } - - private country(): Locator { - return this.getPage().locator('[name="country"]'); - } - - private countrySelect(): Locator { - return this.getPage().locator('.rcx-options .rcx-option:first-child'); - } - - public registeredServer(): Locator { - return this.getPage().locator('input[name=email]'); - } - - public registerButton(): Locator { - return this.getPage().locator('//button[contains(text(), "Register")]'); - } - - public agreementField(): Locator { - return this.getPage().locator('//input[@name="agreement"]/../i[contains(@class, "rcx-check-box")]'); - } - - public standaloneServer(): Locator { - return this.getPage().locator('//button[contains(text(), "Continue as standalone")]'); - } - - public standaloneConfirmText(): Locator { - return this.getPage().locator('//*[contains(text(), "Standalone Server Confirmation")]'); - } - - private fullNameInvalidText(): Locator { - return this.getPage().locator('//input[@name="fullname"]/../following-sibling::span'); - } - - private userNameInvalidText(): Locator { - return this.getPage().locator('//input[@name="username"]/../following-sibling::span'); - } - - private companyEmailInvalidText(): Locator { - return this.getPage().locator('//input[@name="companyEmail"]/../following-sibling::span'); - } - - private passwordInvalidText(): Locator { - return this.getPage().locator('//input[@name="password"]/../../../span[contains(@class, "rcx-field__error")]'); - } - - private industryInvalidSelect(): Locator { - return this.getPage().locator('//div[@name="organizationIndustry"]/../following-sibling::span'); - } - - private sizeInvalidSelect(): Locator { - return this.getPage().locator('//div[@name="organizationSize"]/../following-sibling::span'); - } - - private countryInvalidSelect(): Locator { - return this.getPage().locator('//div[@name="country"]/../following-sibling::span'); - } - - private stepThreeInputInvalidMail(): Locator { - return this.getPage().locator('//input[@name="email"]/../../span[contains(text(), "This field is required")]'); - } - - public async stepTwoSuccess(): Promise { - await this.organizationName().type(reason); - await this.organizationType().click(); - await this.organizationTypeSelect().click(); - await expect(this.getPage().locator('.rcx-options')).toHaveCount(0); - await this.industry().click(); - await this.industrySelect().click(); - await expect(this.getPage().locator('.rcx-options')).toHaveCount(0); - await this.size().click(); - await this.sizeSelect().click(); - await expect(this.getPage().locator('.rcx-options')).toHaveCount(0); - await this.country().click(); - await this.countrySelect().click(); - await this.nextStep().click(); - } - - public async stepThreeSuccess(): Promise { - await this.standaloneServer().click(); - } - - public async stepOneFailedBlankFields(): Promise { - await this.nextStep().click(); - await expect(this.fullNameInvalidText()).toBeVisible(); - await expect(this.userNameInvalidText()).toBeVisible(); - await expect(this.companyEmailInvalidText()).toBeVisible(); - await expect(this.passwordInvalidText()).toBeVisible(); - } - - public async stepOneFailedWithInvalidEmail(adminCredentials: IRegister): Promise { - await this.fullName().type(adminCredentials.name); - await this.userName().type(adminCredentials.name); - await this.companyEmail().type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); - await this.password().type(adminCredentials.password); - await this.nextStep().click(); - await expect(this.companyEmail()).toBeFocused(); - } - - public async stepTwoFailedWithBlankFields(): Promise { - await this.nextStep().click(); - await expect(this.organizationName()).toBeVisible(); - await expect(this.industryInvalidSelect()).toBeVisible(); - await expect(this.sizeInvalidSelect()).toBeVisible(); - await expect(this.countryInvalidSelect()).toBeVisible(); - } - - public async stepThreeFailedWithInvalidField(): Promise { - await this.registeredServer().type(INVALID_EMAIL_WITHOUT_MAIL_PROVIDER); - await this.registeredServer().click({ clickCount: 3 }); - await this.keyboardPress(BACKSPACE); - await expect(this.stepThreeInputInvalidMail()).toBeVisible(); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/SideNav.ts b/apps/meteor/tests/e2e/utils/pageobjects/SideNav.ts deleted file mode 100644 index 0546d1c735d6..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/SideNav.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { expect, Locator } from '@playwright/test'; - -import BasePage from './BasePage'; -import { ENTER } from '../mocks/keyboardKeyMock'; - -export default class SideNav extends BasePage { - public channelType(): Locator { - return this.getPage().locator( - '//*[@id="modal-root"]//*[contains(@class, "rcx-field") and contains(text(), "Private")]/../following-sibling::label/i', - ); - } - - public channelName(): Locator { - return this.getPage().locator('#modal-root [placeholder="Channel Name"]'); - } - - public saveChannelBtn(): Locator { - return this.getPage().locator('//*[@id="modal-root"]//button[contains(text(), "Create")]'); - } - - public sidebarUserMenu(): Locator { - return this.getPage().locator('[data-qa="sidebar-avatar-button"]'); - } - - public statusOnline(): Locator { - return this.getPage().locator('(//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "online")])[1]'); - } - - public statusAway(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "away")]'); - } - - public statusBusy(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "busy")]'); - } - - public statusOffline(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-box--with-inline-elements") and contains(text(), "offline")]'); - } - - public account(): Locator { - return this.getPage().locator('//li[@class="rcx-option"]//div[contains(text(), "My Account")]'); - } - - public admin(): Locator { - return this.getPage().locator('//li[@class="rcx-option"]//div[contains(text(), "Administration")]'); - } - - public omnichannel(): Locator { - return this.getPage().locator('li.rcx-option >> text="Omnichannel"'); - } - - get users(): Locator { - return this.getPage().locator('.flex-nav [href="/admin/users"]'); - } - - public logout(): Locator { - return this.getPage().locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Logout")]'); - } - - public sideNavBar(): Locator { - return this.getPage().locator('.sidebar'); - } - - public flexNav(): Locator { - return this.getPage().locator('.flex-nav'); - } - - public spotlightSearchIcon(): Locator { - return this.getPage().locator('[data-qa="sidebar-search"]'); - } - - public spotlightSearch(): Locator { - return this.getPage().locator('[data-qa="sidebar-search-input"]'); - } - - public spotlightSearchPopUp(): Locator { - return this.getPage().locator('[data-qa="sidebar-search-result"]'); - } - - public newChannelBtnToolbar(): Locator { - return this.getPage().locator('[data-qa="sidebar-create"]'); - } - - public newChannelBtn(): Locator { - return this.getPage().locator('li.rcx-option >> text="Channel"'); - } - - public general(): Locator { - return this.getChannelFromList('general'); - } - - public preferences(): Locator { - return this.getPage().locator('[href="/account/preferences"]'); - } - - public profile(): Locator { - return this.getPage().locator('[href="/account/profile"]'); - } - - public preferencesClose(): Locator { - return this.getPage().locator('//*[contains(@class,"flex-nav")]//i[contains(@class, "rcx-icon--name-cross")]'); - } - - public burgerBtn(): Locator { - return this.getPage().locator('[data-qa-id="burger-menu"]'); - } - - public firstSidebarItemMenu(): Locator { - return this.getPage().locator('[data-qa=sidebar-avatar-button]'); - } - - public returnToMenuInLowResolution(): Locator { - return this.getPage().locator('//button[@aria-label="Close menu"]'); - } - - public async isSideBarOpen(): Promise { - return !!(await this.sideNavBar().getAttribute('style')); - } - - public async openChannel(channelName: string): Promise { - await this.getPage().locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).scrollIntoViewIfNeeded(); - await this.getPage().locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).click(); - await expect(this.getPage().locator('.rcx-room-header')).toContainText(channelName); - } - - public async searchChannel(channelName: string): Promise { - await expect(this.spotlightSearch()).toBeVisible(); - - await this.spotlightSearch().click(); - - await expect(this.spotlightSearch()).toBeFocused(); - await this.spotlightSearch().type(channelName); - - await expect(this.getPage().locator('[data-qa="sidebar-item-title"]', { hasText: channelName }).first()).toContainText(channelName); - - await this.spotlightSearchPopUp().click(); - } - - public getChannelFromList(channelName: any): Locator { - return this.getPage().locator('[data-qa="sidebar-item-title"]', { hasText: channelName }); - } - - private searchUser(): Locator { - return this.getPage().locator('[data-qa="sidebar-search"]'); - } - - private searchInput(): Locator { - return this.getPage().locator('[data-qa="sidebar-search-input"]'); - } - - public async createChannel(channelName: any, isPrivate: any /* isReadOnly*/): Promise { - await this.newChannelBtnToolbar().click(); - - await this.newChannelBtn().click(); - - if (!isPrivate) { - await this.channelType().click(); - } - - await this.channelName().type(channelName); - - await expect(this.saveChannelBtn()).toBeEnabled(); - - await this.saveChannelBtn().click(); - await expect(this.channelType()).not.toBeVisible(); - } - - public async findForChat(target: string): Promise { - await this.searchUser().click(); - await this.searchInput().type(target, { delay: 100 }); - await this.getPage().keyboard.press(ENTER); - } - - public async doLogout(): Promise { - await this.getPage().goto('/home'); - await this.sidebarUserMenu().click(); - await this.logout().click(); - } -} diff --git a/apps/meteor/tests/e2e/utils/pageobjects/index.ts b/apps/meteor/tests/e2e/utils/pageobjects/index.ts deleted file mode 100644 index 3a0e4236cff2..000000000000 --- a/apps/meteor/tests/e2e/utils/pageobjects/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { default as Administration } from './Administration'; -export { default as ChannelCreation } from './ChannelCreation'; -export { default as FlexTab } from './FlexTab'; -export { default as Global } from './Global'; -export { default as LoginPage } from './LoginPage'; -export { default as MainContent } from './MainContent'; -export { default as PreferencesMainContent } from './PreferencesMainContent'; -export { default as SetupWizard } from './SetupWizard'; -export { default as SideNav } from './SideNav'; diff --git a/apps/meteor/tests/e2e/utils/types/ChatContext.ts b/apps/meteor/tests/e2e/utils/types/ChatContext.ts deleted file mode 100644 index ec1a505677d4..000000000000 --- a/apps/meteor/tests/e2e/utils/types/ChatContext.ts +++ /dev/null @@ -1,7 +0,0 @@ -import MainContent from '../pageobjects/MainContent'; -import SideNav from '../pageobjects/SideNav'; - -export type ChatContext = { - mainContent: MainContent; - sideNav: SideNav; -}; From 648dd164bb9aa78093698ef24b2d560321ddd406 Mon Sep 17 00:00:00 2001 From: Felipe <84182706+felipe-rod123@users.noreply.github.com> Date: Wed, 15 Jun 2022 10:22:32 -0300 Subject: [PATCH 03/11] Chore: convert communication methods to Typescript (#25503) Co-authored-by: Guilherme Gazzo Co-authored-by: Guilherme Gazzo --- .../communication/{index.js => index.ts} | 0 .../communication/{methods.js => methods.ts} | 49 +-- .../app/apps/server/communication/uikit.js | 321 ----------------- .../app/apps/server/communication/uikit.ts | 326 ++++++++++++++++++ .../{websockets.js => websockets.ts} | 121 ++++--- apps/meteor/package.json | 2 + yarn.lock | 20 +- 7 files changed, 446 insertions(+), 393 deletions(-) rename apps/meteor/app/apps/server/communication/{index.js => index.ts} (100%) rename apps/meteor/app/apps/server/communication/{methods.js => methods.ts} (55%) delete mode 100644 apps/meteor/app/apps/server/communication/uikit.js create mode 100644 apps/meteor/app/apps/server/communication/uikit.ts rename apps/meteor/app/apps/server/communication/{websockets.js => websockets.ts} (61%) diff --git a/apps/meteor/app/apps/server/communication/index.js b/apps/meteor/app/apps/server/communication/index.ts similarity index 100% rename from apps/meteor/app/apps/server/communication/index.js rename to apps/meteor/app/apps/server/communication/index.ts diff --git a/apps/meteor/app/apps/server/communication/methods.js b/apps/meteor/app/apps/server/communication/methods.ts similarity index 55% rename from apps/meteor/app/apps/server/communication/methods.js rename to apps/meteor/app/apps/server/communication/methods.ts index 1bccb4fbed46..a469408146b2 100644 --- a/apps/meteor/app/apps/server/communication/methods.js +++ b/apps/meteor/app/apps/server/communication/methods.ts @@ -1,27 +1,27 @@ import { Meteor } from 'meteor/meteor'; +import { SettingValue } from '@rocket.chat/core-typings'; import { Settings } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization/server'; import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; +import { AppServerOrchestrator } from '../orchestrator'; -const waitToLoad = function (orch) { - return new Promise((resolve) => { - let id = setInterval(() => { +const waitToLoad = function (orch: AppServerOrchestrator): unknown { + return new Promise((resolve) => { + const id = setInterval(() => { if (orch.isEnabled() && orch.isLoaded()) { clearInterval(id); - id = -1; resolve(); } }, 100); }); }; -const waitToUnload = function (orch) { - return new Promise((resolve) => { - let id = setInterval(() => { +const waitToUnload = function (orch: AppServerOrchestrator): unknown { + return new Promise((resolve) => { + const id = setInterval(() => { if (!orch.isEnabled() && !orch.isLoaded()) { clearInterval(id); - id = -1; resolve(); } }, 100); @@ -29,21 +29,24 @@ const waitToUnload = function (orch) { }; export class AppMethods { - constructor(orch) { - this._orch = orch; + private orch: AppServerOrchestrator; - this._addMethods(); + constructor(orch: AppServerOrchestrator) { + this.orch = orch; + + this.addMethods(); } - isEnabled() { - return typeof this._orch !== 'undefined' && this._orch.isEnabled(); + isEnabled(): SettingValue { + return typeof this.orch !== 'undefined' && this.orch.isEnabled(); } - isLoaded() { - return typeof this._orch !== 'undefined' && this._orch.isEnabled() && this._orch.isLoaded(); + isLoaded(): boolean { + return Boolean(typeof this.orch !== 'undefined' && this.orch.isEnabled() && this.orch.isLoaded()); } - _addMethods() { + private addMethods(): void { + // eslint-disable-next-line @typescript-eslint/no-this-alias const instance = this; Meteor.methods({ @@ -56,13 +59,14 @@ export class AppMethods { }, 'apps/go-enable': twoFactorRequired(function _appsGoEnable() { - if (!Meteor.userId()) { + const uid = Meteor.userId(); + if (!uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'apps/go-enable', }); } - if (!hasPermission(Meteor.userId(), 'manage-apps')) { + if (!hasPermission(uid, 'manage-apps')) { throw new Meteor.Error('error-action-not-allowed', 'Not allowed', { method: 'apps/go-enable', }); @@ -70,17 +74,18 @@ export class AppMethods { Settings.updateValueById('Apps_Framework_enabled', true); - Promise.await(waitToLoad(instance._orch)); + Promise.await(waitToLoad(instance.orch)); }), 'apps/go-disable': twoFactorRequired(function _appsGoDisable() { - if (!Meteor.userId()) { + const uid = Meteor.userId(); + if (!uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'apps/go-enable', }); } - if (!hasPermission(Meteor.userId(), 'manage-apps')) { + if (!hasPermission(uid, 'manage-apps')) { throw new Meteor.Error('error-action-not-allowed', 'Not allowed', { method: 'apps/go-enable', }); @@ -88,7 +93,7 @@ export class AppMethods { Settings.updateValueById('Apps_Framework_enabled', false); - Promise.await(waitToUnload(instance._orch)); + Promise.await(waitToUnload(instance.orch)); }), }); } diff --git a/apps/meteor/app/apps/server/communication/uikit.js b/apps/meteor/app/apps/server/communication/uikit.js deleted file mode 100644 index 27b7c906f8cf..000000000000 --- a/apps/meteor/app/apps/server/communication/uikit.js +++ /dev/null @@ -1,321 +0,0 @@ -import express from 'express'; -import cors from 'cors'; -import rateLimit from 'express-rate-limit'; -import { Meteor } from 'meteor/meteor'; -import { WebApp } from 'meteor/webapp'; -import { UIKitIncomingInteractionType } from '@rocket.chat/apps-engine/definition/uikit'; -import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; - -import { Users } from '../../../models/server'; -import { settings } from '../../../settings/server'; -import { Apps } from '../orchestrator'; -import { UiKitCoreApp } from '../../../../server/sdk'; - -const apiServer = express(); - -apiServer.disable('x-powered-by'); - -let corsEnabled = false; -let allowListOrigins = []; - -settings.watch('API_Enable_CORS', (value) => { - corsEnabled = value; -}); - -settings.watch('API_CORS_Origin', (value) => { - allowListOrigins = value - ? value - .trim() - .split(',') - .map((origin) => String(origin).trim().toLocaleLowerCase()) - : []; -}); - -const corsOptions = { - origin: (origin, callback) => { - if ( - !origin || - !corsEnabled || - allowListOrigins.includes('*') || - allowListOrigins.includes(origin) || - origin === settings.get('Site_Url') - ) { - callback(null, true); - } else { - callback('Not allowed by CORS', false); - } - }, -}; - -WebApp.connectHandlers.use(apiServer); - -// eslint-disable-next-line new-cap -const router = express.Router(); - -const unauthorized = (res) => - res.status(401).send({ - status: 'error', - message: 'You must be logged in to do this.', - }); - -Meteor.startup(() => { - // use specific rate limit of 600 (which is 60 times the default limits) requests per minute (around 10/second) - const apiLimiter = rateLimit({ - windowMs: settings.get('API_Enable_Rate_Limiter_Limit_Time_Default'), - max: settings.get('API_Enable_Rate_Limiter_Limit_Calls_Default') * 60, - skip: () => - settings.get('API_Enable_Rate_Limiter') !== true || - (process.env.NODE_ENV === 'development' && settings.get('API_Enable_Rate_Limiter_Dev') !== true), - }); - router.use(apiLimiter); -}); - -router.use((req, res, next) => { - const { 'x-user-id': userId, 'x-auth-token': authToken, 'x-visitor-token': visitorToken } = req.headers; - - if (userId && authToken) { - req.user = Users.findOneByIdAndLoginToken(userId, authToken); - req.userId = req.user._id; - } - - if (visitorToken) { - req.visitor = Apps.getConverters().get('visitors').convertByToken(visitorToken); - } - - if (!req.user && !req.visitor) { - return unauthorized(res); - } - - next(); -}); - -apiServer.use('/api/apps/ui.interaction/', cors(corsOptions), router); - -const getPayloadForType = (type, req) => { - if (type === UIKitIncomingInteractionType.BLOCK) { - const { type, actionId, triggerId, mid, rid, payload, container } = req.body; - - const { visitor, user } = req; - const room = rid; // orch.getConverters().get('rooms').convertById(rid); - const message = mid; - - return { - type, - container, - actionId, - message, - triggerId, - payload, - user, - visitor, - room, - }; - } - - if (type === UIKitIncomingInteractionType.VIEW_CLOSED) { - const { - type, - actionId, - payload: { view, isCleared }, - } = req.body; - - const { user } = req; - - return { - type, - actionId, - user, - payload: { - view, - isCleared, - }, - }; - } - - if (type === UIKitIncomingInteractionType.VIEW_SUBMIT) { - const { type, actionId, triggerId, payload } = req.body; - - const { user } = req; - - return { - type, - actionId, - triggerId, - payload, - user, - }; - } - - throw new Error('Type not supported'); -}; - -router.post('/:appId', async (req, res, next) => { - const { appId } = req.params; - - const isCore = await UiKitCoreApp.isRegistered(appId); - if (!isCore) { - return next(); - } - - const { type } = req.body; - - try { - const payload = { - ...getPayloadForType(type, req), - appId, - }; - - const result = await UiKitCoreApp[type](payload); - - res.send(result); - } catch (e) { - console.error('ops', e); - res.status(500).send({ error: e.message }); - } -}); - -const appsRoutes = (orch) => (req, res) => { - const { appId } = req.params; - - const { type } = req.body; - - switch (type) { - case UIKitIncomingInteractionType.BLOCK: { - const { type, actionId, triggerId, mid, rid, payload, container } = req.body; - - const { visitor } = req; - const room = orch.getConverters().get('rooms').convertById(rid); - const user = orch.getConverters().get('users').convertToApp(req.user); - const message = mid && orch.getConverters().get('messages').convertById(mid); - - const action = { - type, - container, - appId, - actionId, - message, - triggerId, - payload, - user, - visitor, - room, - }; - - try { - const eventInterface = !visitor ? AppInterface.IUIKitInteractionHandler : AppInterface.IUIKitLivechatInteractionHandler; - - const result = Promise.await(orch.triggerEvent(eventInterface, action)); - - res.send(result); - } catch (e) { - res.status(500).send(e.message); - } - break; - } - - case UIKitIncomingInteractionType.VIEW_CLOSED: { - const { - type, - actionId, - payload: { view, isCleared }, - } = req.body; - - const user = orch.getConverters().get('users').convertToApp(req.user); - - const action = { - type, - appId, - actionId, - user, - payload: { - view, - isCleared, - }, - }; - - try { - Promise.await(orch.triggerEvent('IUIKitInteractionHandler', action)); - - res.sendStatus(200); - } catch (e) { - res.status(500).send(e.message); - } - break; - } - - case UIKitIncomingInteractionType.VIEW_SUBMIT: { - const { type, actionId, triggerId, payload } = req.body; - - const user = orch.getConverters().get('users').convertToApp(req.user); - - const action = { - type, - appId, - actionId, - triggerId, - payload, - user, - }; - - try { - const result = Promise.await(orch.triggerEvent('IUIKitInteractionHandler', action)); - - res.send(result); - } catch (e) { - res.status(500).send(e.message); - } - break; - } - - case UIKitIncomingInteractionType.ACTION_BUTTON: { - const { - type, - actionId, - triggerId, - rid, - mid, - payload: { context }, - } = req.body; - - const room = orch.getConverters().get('rooms').convertById(rid); - const user = orch.getConverters().get('users').convertToApp(req.user); - const message = mid && orch.getConverters().get('messages').convertById(mid); - - const action = { - type, - appId, - actionId, - triggerId, - user, - room, - message, - payload: { - context, - }, - }; - - try { - const result = Promise.await(orch.triggerEvent('IUIKitInteractionHandler', action)); - - res.send(result); - } catch (e) { - res.status(500).send(e.message); - } - break; - } - - default: { - res.status(400).send({ error: 'Unknown action' }); - } - } - - // TODO: validate payloads per type -}; - -export class AppUIKitInteractionApi { - constructor(orch) { - this.orch = orch; - - router.post('/:appId', appsRoutes(orch)); - } -} diff --git a/apps/meteor/app/apps/server/communication/uikit.ts b/apps/meteor/app/apps/server/communication/uikit.ts new file mode 100644 index 000000000000..266cc3f841a3 --- /dev/null +++ b/apps/meteor/app/apps/server/communication/uikit.ts @@ -0,0 +1,326 @@ +import express, { Request, Response } from 'express'; +import cors from 'cors'; +import rateLimit from 'express-rate-limit'; +import { Meteor } from 'meteor/meteor'; +import { WebApp } from 'meteor/webapp'; +import { UIKitIncomingInteractionType } from '@rocket.chat/apps-engine/definition/uikit'; +import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; + +import { Users } from '../../../models/server'; +import { settings } from '../../../settings/server'; +import { Apps, AppServerOrchestrator } from '../orchestrator'; +import { UiKitCoreApp } from '../../../../server/sdk'; + +const apiServer = express(); + +apiServer.disable('x-powered-by'); + +let corsEnabled = false; +let allowListOrigins: string[] = []; + +settings.watch('API_Enable_CORS', (value: boolean) => { + corsEnabled = value; +}); + +settings.watch('API_CORS_Origin', (value: string) => { + allowListOrigins = value + ? value + .trim() + .split(',') + .map((origin) => String(origin).trim().toLocaleLowerCase()) + : []; +}); + +WebApp.connectHandlers.use(apiServer); + +// eslint-disable-next-line new-cap +const router = express.Router(); + +const unauthorized = (res: Response): unknown => + res.status(401).send({ + status: 'error', + message: 'You must be logged in to do this.', + }); + +Meteor.startup(() => { + // use specific rate limit of 600 (which is 60 times the default limits) requests per minute (around 10/second) + const apiLimiter = rateLimit({ + windowMs: settings.get('API_Enable_Rate_Limiter_Limit_Time_Default'), + max: (settings.get('API_Enable_Rate_Limiter_Limit_Calls_Default') as number) * 60, + skip: () => + settings.get('API_Enable_Rate_Limiter') !== true || + (process.env.NODE_ENV === 'development' && settings.get('API_Enable_Rate_Limiter_Dev') !== true), + }); + router.use(apiLimiter); +}); + +router.use((req, res, next) => { + const { 'x-user-id': userId, 'x-auth-token': authToken, 'x-visitor-token': visitorToken } = req.headers; + + if (userId && authToken) { + req.body.user = Users.findOneByIdAndLoginToken(userId, authToken); + req.body.userId = req.body.user._id; + } + + if (visitorToken) { + req.body.visitor = Apps.getConverters()?.get('visitors').convertByToken(visitorToken); + } + + if (!req.body.user && !req.body.visitor) { + return unauthorized(res); + } + + next(); +}); + +const corsOptions = { + origin: (origin: string | undefined, callback: Function): void => { + if ( + !origin || + !corsEnabled || + allowListOrigins.includes('*') || + allowListOrigins.includes(origin) || + origin === settings.get('Site_Url') + ) { + callback(null, true); + } else { + callback('Not allowed by CORS', false); + } + }, +}; + +apiServer.use('/api/apps/ui.interaction/', cors(corsOptions), router); // didn't have the rateLimiter option + +const getPayloadForType = (type: UIKitIncomingInteractionType, req: Request): {} => { + if (type === UIKitIncomingInteractionType.BLOCK) { + const { type, actionId, triggerId, mid, rid, payload, container } = req.body; + + const { visitor, user } = req.body; + const room = rid; // orch.getConverters().get('rooms').convertById(rid); + const message = mid; + + return { + type, + container, + actionId, + message, + triggerId, + payload, + user, + visitor, + room, + }; + } + + if (type === UIKitIncomingInteractionType.VIEW_CLOSED) { + const { + type, + actionId, + payload: { view, isCleared }, + } = req.body; + + const { user } = req.body; + + return { + type, + actionId, + user, + payload: { + view, + isCleared, + }, + }; + } + + if (type === UIKitIncomingInteractionType.VIEW_SUBMIT) { + const { type, actionId, triggerId, payload } = req.body; + + const { user } = req.body; + + return { + type, + actionId, + triggerId, + payload, + user, + }; + } + + throw new Error('Type not supported'); +}; + +router.post('/:appId', async (req, res, next) => { + const { appId } = req.params; + + const isCore = await UiKitCoreApp.isRegistered(appId); + if (!isCore) { + return next(); + } + + // eslint-disable-next-line prefer-destructuring + const type: UIKitIncomingInteractionType = req.body.type; + + try { + const payload = { + ...getPayloadForType(type, req), + appId, + }; + + const result = await (UiKitCoreApp as any)[type](payload); // TO-DO: fix type + + res.send(result); + } catch (e) { + if (e instanceof Error) res.status(500).send({ error: e.message }); + else res.status(500).send({ error: e }); + } +}); + +const appsRoutes = + (orch: AppServerOrchestrator) => + (req: Request, res: Response): void => { + const { appId } = req.params; + + const { type } = req.body; + + switch (type) { + case UIKitIncomingInteractionType.BLOCK: { + const { type, actionId, triggerId, mid, rid, payload, container } = req.body; + + const { visitor } = req.body; + const room = orch.getConverters()?.get('rooms').convertById(rid); + const user = orch.getConverters()?.get('users').convertToApp(req.body.user); + const message = mid && orch.getConverters()?.get('messages').convertById(mid); + + const action = { + type, + container, + appId, + actionId, + message, + triggerId, + payload, + user, + visitor, + room, + }; + + try { + const eventInterface = !visitor ? AppInterface.IUIKitInteractionHandler : AppInterface.IUIKitLivechatInteractionHandler; + + const result = Promise.await(orch.triggerEvent(eventInterface, action)); + + res.send(result); + } catch (e) { + res.status(500).send(e); // e.message + } + break; + } + + case UIKitIncomingInteractionType.VIEW_CLOSED: { + const { + type, + actionId, + payload: { view, isCleared }, + } = req.body; + + const user = orch.getConverters()?.get('users').convertToApp(req.body.user); + + const action = { + type, + appId, + actionId, + user, + payload: { + view, + isCleared, + }, + }; + + try { + Promise.await(orch.triggerEvent('IUIKitInteractionHandler', action)); + + res.sendStatus(200); + } catch (e) { + res.status(500).send(e); // e.message + } + break; + } + + case UIKitIncomingInteractionType.VIEW_SUBMIT: { + const { type, actionId, triggerId, payload } = req.body; + + const user = orch.getConverters()?.get('users').convertToApp(req.body.user); + + const action = { + type, + appId, + actionId, + triggerId, + payload, + user, + }; + + try { + const result = Promise.await(orch.triggerEvent('IUIKitInteractionHandler', action)); + + res.send(result); + } catch (e) { + res.status(500).send(e); // e.message + } + break; + } + + case UIKitIncomingInteractionType.ACTION_BUTTON: { + const { + type, + actionId, + triggerId, + rid, + mid, + payload: { context }, + } = req.body; + + const room = orch.getConverters()?.get('rooms').convertById(rid); + const user = orch.getConverters()?.get('users').convertToApp(req.body.user); + const message = mid && orch.getConverters()?.get('messages').convertById(mid); + + const action = { + type, + appId, + actionId, + triggerId, + user, + room, + message, + payload: { + context, + }, + }; + + try { + const result = Promise.await(orch.triggerEvent('IUIKitInteractionHandler', action)); + + res.send(result); + } catch (e) { + res.status(500).send(e); // e.message + } + break; + } + + default: { + res.status(400).send({ error: 'Unknown action' }); + } + } + + // TODO: validate payloads per type + }; + +export class AppUIKitInteractionApi { + orch: AppServerOrchestrator; + + constructor(orch: AppServerOrchestrator) { + this.orch = orch; + + router.post('/:appId', appsRoutes(orch)); + } +} diff --git a/apps/meteor/app/apps/server/communication/websockets.js b/apps/meteor/app/apps/server/communication/websockets.ts similarity index 61% rename from apps/meteor/app/apps/server/communication/websockets.js rename to apps/meteor/app/apps/server/communication/websockets.ts index 005e74345733..cfdfdf2b77b5 100644 --- a/apps/meteor/app/apps/server/communication/websockets.js +++ b/apps/meteor/app/apps/server/communication/websockets.ts @@ -1,23 +1,35 @@ -import { AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus'; +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { AppStatus, AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus'; +import { ISetting } from '@rocket.chat/core-typings'; +import { IStreamer } from 'meteor/rocketchat:streamer'; import { SystemLogger } from '../../../../server/lib/logger/system'; import notifications from '../../../notifications/server/lib/Notifications'; - -export const AppEvents = Object.freeze({ - APP_ADDED: 'app/added', - APP_REMOVED: 'app/removed', - APP_UPDATED: 'app/updated', - APP_STATUS_CHANGE: 'app/statusUpdate', - APP_SETTING_UPDATED: 'app/settingUpdated', - COMMAND_ADDED: 'command/added', - COMMAND_DISABLED: 'command/disabled', - COMMAND_UPDATED: 'command/updated', - COMMAND_REMOVED: 'command/removed', - ACTIONS_CHANGED: 'actions/changed', -}); +import { AppServerOrchestrator } from '../orchestrator'; + +export enum AppEvents { + APP_ADDED = 'app/added', + APP_REMOVED = 'app/removed', + APP_UPDATED = 'app/updated', + APP_STATUS_CHANGE = 'app/statusUpdate', + APP_SETTING_UPDATED = 'app/settingUpdated', + COMMAND_ADDED = 'command/added', + COMMAND_DISABLED = 'command/disabled', + COMMAND_UPDATED = 'command/updated', + COMMAND_REMOVED = 'command/removed', + ACTIONS_CHANGED = 'actions/changed', +} export class AppServerListener { - constructor(orch, engineStreamer, clientStreamer, received) { + private orch: AppServerOrchestrator; + + engineStreamer: IStreamer; + + clientStreamer: IStreamer; + + received; + + constructor(orch: AppServerOrchestrator, engineStreamer: IStreamer, clientStreamer: IStreamer, received: Map) { this.orch = orch; this.engineStreamer = engineStreamer; this.clientStreamer = clientStreamer; @@ -36,13 +48,13 @@ export class AppServerListener { this.engineStreamer.on(AppEvents.COMMAND_REMOVED, this.onCommandRemoved.bind(this)); } - async onAppAdded(appId) { - await this.orch.getManager().loadOne(appId); + async onAppAdded(appId: string): Promise { + await (this.orch.getManager()! as any).loadOne(appId); // TO-DO: fix type this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_ADDED, appId); } - async onAppStatusUpdated({ appId, status }) { - const app = this.orch.getManager().getOneById(appId); + async onAppStatusUpdated({ appId, status }: { appId: string; status: AppStatus }): Promise { + const app = this.orch.getManager()?.getOneById(appId); if (!app || app.getStatus() === status) { return; @@ -55,70 +67,81 @@ export class AppServerListener { }); if (AppStatusUtils.isEnabled(status)) { - await this.orch.getManager().enable(appId).catch(SystemLogger.error); + await this.orch.getManager()?.enable(appId).catch(SystemLogger.error); this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_STATUS_CHANGE, { appId, status }); } else if (AppStatusUtils.isDisabled(status)) { - await this.orch.getManager().disable(appId, status, true).catch(SystemLogger.error); + await this.orch.getManager()?.disable(appId, status, true).catch(SystemLogger.error); this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_STATUS_CHANGE, { appId, status }); } } - async onAppSettingUpdated({ appId, setting }) { - this.received.set(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting.id}`, { + async onAppSettingUpdated({ appId, setting }: { appId: string; setting: ISetting }): Promise { + this.received.set(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting._id}`, { appId, setting, when: new Date(), }); - await this.orch.getManager().getSettingsManager().updateAppSetting(appId, setting); + await this.orch + .getManager()! + .getSettingsManager() + .updateAppSetting(appId, setting as any); // TO-DO: fix type of `setting` this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_SETTING_UPDATED, { appId }); } - async onAppUpdated(appId) { + async onAppUpdated(appId: string): Promise { this.received.set(`${AppEvents.APP_UPDATED}_${appId}`, { appId, when: new Date() }); - const storageItem = await this.orch.getStorage().retrieveOne(appId); + const storageItem = await this.orch.getStorage()!.retrieveOne(appId); - const appPackage = await this.orch.getAppSourceStorage().fetch(storageItem); + const appPackage = await this.orch.getAppSourceStorage()!.fetch(storageItem); - await this.orch.getManager().updateLocal(storageItem, appPackage); + await this.orch.getManager()!.updateLocal(storageItem, appPackage); this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_UPDATED, appId); } - async onAppRemoved(appId) { - const app = this.orch.getManager().getOneById(appId); + async onAppRemoved(appId: string): Promise { + const app = this.orch.getManager()!.getOneById(appId); if (!app) { return; } - await this.orch.getManager().removeLocal(appId); + await this.orch.getManager()!.removeLocal(appId); this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_REMOVED, appId); } - async onCommandAdded(command) { + async onCommandAdded(command: string): Promise { this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_ADDED, command); } - async onCommandDisabled(command) { + async onCommandDisabled(command: string): Promise { this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_DISABLED, command); } - async onCommandUpdated(command) { + async onCommandUpdated(command: string): Promise { this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_UPDATED, command); } - async onCommandRemoved(command) { + async onCommandRemoved(command: string): Promise { this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_REMOVED, command); } - async onActionsChanged() { + async onActionsChanged(): Promise { this.clientStreamer.emitWithoutBroadcast(AppEvents.ACTIONS_CHANGED); } } export class AppServerNotifier { - constructor(orch) { + engineStreamer: IStreamer; + + clientStreamer: IStreamer; + + received: Map; + + listener: AppServerListener; + + constructor(orch: AppServerOrchestrator) { this.engineStreamer = notifications.streamAppsEngine; // This is used to broadcast to the web clients @@ -128,17 +151,17 @@ export class AppServerNotifier { this.listener = new AppServerListener(orch, this.engineStreamer, this.clientStreamer, this.received); } - async appAdded(appId) { + async appAdded(appId: string): Promise { this.engineStreamer.emit(AppEvents.APP_ADDED, appId); this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_ADDED, appId); } - async appRemoved(appId) { + async appRemoved(appId: string): Promise { this.engineStreamer.emit(AppEvents.APP_REMOVED, appId); this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_REMOVED, appId); } - async appUpdated(appId) { + async appUpdated(appId: string): Promise { if (this.received.has(`${AppEvents.APP_UPDATED}_${appId}`)) { this.received.delete(`${AppEvents.APP_UPDATED}_${appId}`); return; @@ -148,7 +171,7 @@ export class AppServerNotifier { this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_UPDATED, appId); } - async appStatusUpdated(appId, status) { + async appStatusUpdated(appId: string, status: AppStatus): Promise { if (this.received.has(`${AppEvents.APP_STATUS_CHANGE}_${appId}`)) { const details = this.received.get(`${AppEvents.APP_STATUS_CHANGE}_${appId}`); if (details.status === status) { @@ -161,9 +184,9 @@ export class AppServerNotifier { this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_STATUS_CHANGE, { appId, status }); } - async appSettingsChange(appId, setting) { - if (this.received.has(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting.id}`)) { - this.received.delete(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting.id}`); + async appSettingsChange(appId: string, setting: ISetting): Promise { + if (this.received.has(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting._id}`)) { + this.received.delete(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting._id}`); return; } @@ -171,27 +194,27 @@ export class AppServerNotifier { this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_SETTING_UPDATED, { appId }); } - async commandAdded(command) { + async commandAdded(command: string): Promise { this.engineStreamer.emit(AppEvents.COMMAND_ADDED, command); this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_ADDED, command); } - async commandDisabled(command) { + async commandDisabled(command: string): Promise { this.engineStreamer.emit(AppEvents.COMMAND_DISABLED, command); this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_DISABLED, command); } - async commandUpdated(command) { + async commandUpdated(command: string): Promise { this.engineStreamer.emit(AppEvents.COMMAND_UPDATED, command); this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_UPDATED, command); } - async commandRemoved(command) { + async commandRemoved(command: string): Promise { this.engineStreamer.emit(AppEvents.COMMAND_REMOVED, command); this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_REMOVED, command); } - async actionsChanged() { + async actionsChanged(): Promise { this.clientStreamer.emitWithoutBroadcast(AppEvents.ACTIONS_CHANGED); } } diff --git a/apps/meteor/package.json b/apps/meteor/package.json index f73336794d10..630192e25146 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -91,9 +91,11 @@ "@types/clipboard": "^2.0.7", "@types/codemirror": "^5", "@types/cookie-parser": "^1.4.2", + "@types/cors": "^2.8.5", "@types/dompurify": "^2.2.2", "@types/ejson": "^2.2.0", "@types/express": "^4.17.12", + "@types/express-rate-limit": "^5.1.3", "@types/fibers": "^3.1.1", "@types/google-libphonenumber": "^7.4.21", "@types/imap": "^0.8.35", diff --git a/yarn.lock b/yarn.lock index 8509f25ad3cc..04f22677f49c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3782,9 +3782,11 @@ __metadata: "@types/codemirror": ^5 "@types/cookie": ^0.5.1 "@types/cookie-parser": ^1.4.2 + "@types/cors": ^2.8.5 "@types/dompurify": ^2.2.2 "@types/ejson": ^2.2.0 "@types/express": ^4.17.12 + "@types/express-rate-limit": ^5.1.3 "@types/fibers": ^3.1.1 "@types/google-libphonenumber": ^7.4.21 "@types/imap": ^0.8.35 @@ -5824,6 +5826,13 @@ __metadata: languageName: node linkType: hard +"@types/cors@npm:^2.8.5": + version: 2.8.12 + resolution: "@types/cors@npm:2.8.12" + checksum: 8c45f112c7d1d2d831b4b266f2e6ed33a1887a35dcbfe2a18b28370751fababb7cd045e745ef84a523c33a25932678097bf79afaa367c6cb3fa0daa7a6438257 + languageName: node + linkType: hard + "@types/dompurify@npm:^2.2.2": version: 2.3.3 resolution: "@types/dompurify@npm:2.3.3" @@ -5881,6 +5890,15 @@ __metadata: languageName: node linkType: hard +"@types/express-rate-limit@npm:^5.1.3": + version: 5.1.3 + resolution: "@types/express-rate-limit@npm:5.1.3" + dependencies: + "@types/express": "*" + checksum: 44c0b79e48a9416309459e4aad7dbac6bdf0e1777ed2d9620d1512eecfacbd32d7d1b86789c116ced32e926b595ea55d212076b46ea934676ab352e260233686 + languageName: node + linkType: hard + "@types/express-serve-static-core@npm:^4.17.18": version: 4.17.28 resolution: "@types/express-serve-static-core@npm:4.17.28" @@ -7561,7 +7579,7 @@ __metadata: human-interval: ~1.0.0 moment-timezone: ~0.5.27 mongodb: ~3.5.0 - checksum: f5f68008298f9482631f1f494e392cd6b8ba7971a3b0ece81ae2abe60f53d67973ff4476156fa5c9c41b8b58c4ccd284e95c545e0523996dfd05f9a80b843e07 + checksum: cc8c1bbba7545628d9d039c58e701ff65cf07f241f035b731716eec0d5ef906ce09d60c3b321bbfb9e6c641994d1afd23aaeb92d645b33bf7be9942f13574173 languageName: node linkType: hard From 208b6ad952b7c844c543e443fb1a8313c04541f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Guimar=C3=A3es=20Ribeiro?= Date: Wed, 15 Jun 2022 11:55:28 -0300 Subject: [PATCH 04/11] Chore: Translate admin helpers to TS (#25690) Co-authored-by: Guilherme Gazzo --- .../app/apps/client/@types/IOrchestrator.ts | 23 +------ apps/meteor/app/apps/client/orchestrator.ts | 42 ------------ .../client/views/admin/apps/PriceDisplay.tsx | 6 +- .../admin/apps/{helpers.js => helpers.ts} | 68 ++++++++++++------- .../views/admin/apps/hooks/useAppInfo.ts | 2 +- .../rocketchat-i18n/i18n/en.i18n.json | 1 + packages/core-typings/src/Apps.ts | 51 +++++++++++--- 7 files changed, 94 insertions(+), 99 deletions(-) rename apps/meteor/client/views/admin/apps/{helpers.js => helpers.ts} (68%) diff --git a/apps/meteor/app/apps/client/@types/IOrchestrator.ts b/apps/meteor/app/apps/client/@types/IOrchestrator.ts index 55400407a946..848dcdfabf82 100644 --- a/apps/meteor/app/apps/client/@types/IOrchestrator.ts +++ b/apps/meteor/app/apps/client/@types/IOrchestrator.ts @@ -1,4 +1,5 @@ import { ISetting } from '@rocket.chat/apps-engine/definition/settings/ISetting'; +import { App } from '@rocket.chat/core-typings'; export interface IDetailedDescription { raw: string; @@ -109,26 +110,6 @@ export enum EAppPurchaseType { PurchaseTypeSubscription = 'subscription', } -export interface IAppFromMarketplace { - appId: string; - latest: ILatest; - isAddon: boolean; - isEnterpriseOnly: boolean; - isBundle: boolean; - bundledAppIds: any[]; - bundledIn: IBundledIn[]; - isPurchased: boolean; - isSubscribed: boolean; - subscriptionInfo: ISubscriptionInfo; - price: number; - purchaseType: EAppPurchaseType; - isUsageBased: boolean; - createdAt: Date; - modifiedAt: Date; - pricingPlans: IPricingPlan[]; - addonId: string; -} - export interface ILanguageInfo { Params: string; Description: string; @@ -163,7 +144,7 @@ export interface ICategory { // } export interface IAppSynced { - app: IAppFromMarketplace; + app: App; success: boolean; } diff --git a/apps/meteor/app/apps/client/orchestrator.ts b/apps/meteor/app/apps/client/orchestrator.ts index ea224409fbc4..155027a31f94 100644 --- a/apps/meteor/app/apps/client/orchestrator.ts +++ b/apps/meteor/app/apps/client/orchestrator.ts @@ -15,8 +15,6 @@ import { settings } from '../../settings/client'; import { CachedCollectionManager } from '../../ui-cached-collection'; import { createDeferredValue } from '../lib/misc/DeferredValue'; import { - IPricingPlan, - EAppPurchaseType, // IAppFromMarketplace, IAppLanguage, IAppExternalURL, @@ -24,10 +22,6 @@ import { // IAppSynced, // IAppScreenshots, // IScreenshot, - IAuthor, - IDetailedChangelog, - IDetailedDescription, - ISubscriptionInfo, } from './@types/IOrchestrator'; import { AppWebsocketReceiver } from './communication'; import { handleI18nResources } from './i18n'; @@ -35,42 +29,6 @@ import { RealAppsEngineUIHost } from './RealAppsEngineUIHost'; import { APIClient } from '../../utils/client'; import { hasAtLeastOnePermission } from '../../authorization/client'; -export interface IAppsFromMarketplace { - price: number; - pricingPlans: IPricingPlan[]; - purchaseType: EAppPurchaseType; - isEnterpriseOnly: boolean; - modifiedAt: Date; - internalId: string; - id: string; - name: string; - nameSlug: string; - version: string; - categories: string[]; - description: string; - detailedDescription: IDetailedDescription; - detailedChangelog: IDetailedChangelog; - requiredApiVersion: string; - author: IAuthor; - classFile: string; - iconFile: string; - iconFileData: string; - status: string; - isVisible: boolean; - createdDate: Date; - modifiedDate: Date; - isPurchased: boolean; - isSubscribed: boolean; - subscriptionInfo: ISubscriptionInfo; - compiled: boolean; - compileJobId: string; - changesNote: string; - languages: string[]; - privacyPolicySummary: string; - internalChangesNote: string; - permissions: IPermission[]; -} - class AppClientOrchestrator { private _appClientUIHost: RealAppsEngineUIHost; diff --git a/apps/meteor/client/views/admin/apps/PriceDisplay.tsx b/apps/meteor/client/views/admin/apps/PriceDisplay.tsx index e185850800e4..8ad3ac19fffd 100644 --- a/apps/meteor/client/views/admin/apps/PriceDisplay.tsx +++ b/apps/meteor/client/views/admin/apps/PriceDisplay.tsx @@ -1,4 +1,4 @@ -import type { PricingPlan } from '@rocket.chat/core-typings'; +import type { AppPricingPlan } from '@rocket.chat/core-typings'; import { Box } from '@rocket.chat/fuselage'; import { TranslationKey, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, useMemo } from 'react'; @@ -7,7 +7,7 @@ import { formatPricingPlan, formatPrice } from './helpers'; type PriceDisplayProps = { purchaseType: string; - pricingPlans: PricingPlan[]; + pricingPlans: AppPricingPlan[]; price: number; showType?: boolean; marginInline?: string; @@ -20,7 +20,7 @@ type FormattedPriceAndPlan = { price: string; }; -const formatPriceAndPurchaseType = (purchaseType: string, pricingPlans: PricingPlan[], price: number): FormattedPriceAndPlan => { +const formatPriceAndPurchaseType = (purchaseType: string, pricingPlans: AppPricingPlan[], price: number): FormattedPriceAndPlan => { if (purchaseType === 'subscription') { const type = 'Subscription'; if (!pricingPlans || !Array.isArray(pricingPlans) || pricingPlans.length === 0) { diff --git a/apps/meteor/client/views/admin/apps/helpers.js b/apps/meteor/client/views/admin/apps/helpers.ts similarity index 68% rename from apps/meteor/client/views/admin/apps/helpers.js rename to apps/meteor/client/views/admin/apps/helpers.ts index 1314bd1b56a7..bf44e5ebd370 100644 --- a/apps/meteor/client/views/admin/apps/helpers.js +++ b/apps/meteor/client/views/admin/apps/helpers.ts @@ -1,4 +1,6 @@ import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; +import { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; +import { App, AppPricingPlan } from '@rocket.chat/core-typings'; import semver from 'semver'; import { Utilities } from '../../../../app/apps/lib/misc/Utilities'; @@ -14,26 +16,29 @@ const appErroredStatuses = [ AppStatus.INVALID_LICENSE_DISABLED, ]; -export const apiCurlGetter = (absoluteUrl) => (method, api) => { - const example = api.examples[method] || {}; - return Utilities.curl({ - url: absoluteUrl(api.computedPath), - method, - params: example.params, - query: example.query, - content: example.content, - headers: example.headers, - }).split('\n'); -}; +export const apiCurlGetter = + (absoluteUrl: (path: string) => string) => + (method: string, api: IApiEndpointMetadata): string[] => { + const example = (api.examples && api.examples[method]) || {}; + return Utilities.curl({ + url: absoluteUrl(api.computedPath), + method, + params: example.params, + query: example.query, + content: example.content, + headers: example.headers, + auth: '', + }).split('\n'); + }; -export function handleInstallError(apiError) { +export function handleInstallError(apiError: { xhr: { responseJSON: { status: any; messages: any; error: any; payload?: any } } }): void { if (!apiError.xhr || !apiError.xhr.responseJSON) { return; } const { status, messages, error, payload = null } = apiError.xhr.responseJSON; - let message; + let message: string; switch (status) { case 'storage_error': @@ -41,7 +46,7 @@ export function handleInstallError(apiError) { break; case 'app_user_error': message = messages.join(''); - if (payload && payload.username) { + if (payload?.username) { message = t('Apps_User_Already_Exists', { username: payload.username }); } break; @@ -49,20 +54,23 @@ export function handleInstallError(apiError) { if (error) { message = error; } else { - message = 'There has been an error installing the app'; + message = t('There_has_been_an_error_installing_the_app'); } } dispatchToastMessage({ type: 'error', message }); } -const shouldHandleErrorAsWarning = (message) => { +const shouldHandleErrorAsWarning = (message: string): boolean => { const warnings = ['Could not reach the Marketplace']; return warnings.includes(message); }; -export const handleAPIError = (error) => { +export const handleAPIError = (error: { + xhr: { responseJSON: { status: any; messages: any; error: any; payload?: any } }; + message: string; +}): void => { const message = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message; if (shouldHandleErrorAsWarning(message)) { @@ -73,7 +81,7 @@ export const handleAPIError = (error) => { dispatchToastMessage({ type: 'error', message }); }; -export const warnStatusChange = (appName, status) => { +export const warnStatusChange = (appName: string, status: AppStatus): void => { if (appErroredStatuses.includes(status)) { dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) }); return; @@ -82,6 +90,12 @@ export const warnStatusChange = (appName, status) => { dispatchToastMessage({ type: 'info', message: (t(`App_status_${status}`), appName) }); }; +type appButtonPropsResponse = { + action: 'update' | 'install' | 'purchase'; + icon?: 'reload'; + label: 'Update' | 'Install' | 'Subscribe' | 'See Pricing' | 'Try now' | 'Buy'; +}; + export const appButtonProps = ({ installed, version, @@ -92,7 +106,7 @@ export const appButtonProps = ({ subscriptionInfo, pricingPlans, isEnterpriseOnly, -}) => { +}: App): appButtonPropsResponse | undefined => { const canUpdate = installed && version && marketplaceVersion && semver.lt(version, marketplaceVersion); if (canUpdate) { return { @@ -153,12 +167,18 @@ export const appButtonProps = ({ }; }; -export const appStatusSpanProps = ({ installed, status, subscriptionInfo }) => { +type appStatusSpanPropsResponse = { + type?: 'failed' | 'warning'; + icon: 'warning' | 'ban' | 'checkmark-circled' | 'check'; + label: 'Config Needed' | 'Failed' | 'Disabled' | 'Trial period' | 'Enabled'; +}; + +export const appStatusSpanProps = ({ installed, status, subscriptionInfo }: App): appStatusSpanPropsResponse | undefined => { if (!installed) { return; } - const isFailed = appErroredStatuses.includes(status); + const isFailed = status && appErroredStatuses.includes(status); if (isFailed) { return { type: 'failed', @@ -167,7 +187,7 @@ export const appStatusSpanProps = ({ installed, status, subscriptionInfo }) => { }; } - const isEnabled = appEnabledStatuses.includes(status); + const isEnabled = status && appEnabledStatuses.includes(status); if (!isEnabled) { return { type: 'warning', @@ -190,9 +210,9 @@ export const appStatusSpanProps = ({ installed, status, subscriptionInfo }) => { }; }; -export const formatPrice = (price) => `\$${Number.parseFloat(price).toFixed(2)}`; +export const formatPrice = (price: number): string => `\$${price.toFixed(2)}`; -export const formatPricingPlan = ({ strategy, price, tiers = [], trialDays }) => { +export const formatPricingPlan = ({ strategy, price, tiers = [], trialDays }: AppPricingPlan): string => { const { perUnit = false } = (Array.isArray(tiers) && tiers.find((tier) => tier.price === price)) || {}; const pricingPlanTranslationString = [ diff --git a/apps/meteor/client/views/admin/apps/hooks/useAppInfo.ts b/apps/meteor/client/views/admin/apps/hooks/useAppInfo.ts index 313f4291bf40..499b0d323341 100644 --- a/apps/meteor/client/views/admin/apps/hooks/useAppInfo.ts +++ b/apps/meteor/client/views/admin/apps/hooks/useAppInfo.ts @@ -1,3 +1,4 @@ +import { App } from '@rocket.chat/core-typings'; import { useEndpoint, EndpointFunction } from '@rocket.chat/ui-contexts'; import { useState, useEffect, useContext } from 'react'; @@ -5,7 +6,6 @@ import { ISettings } from '../../../../../app/apps/client/@types/IOrchestrator'; import { Apps } from '../../../../../app/apps/client/orchestrator'; import { AppsContext } from '../AppsContext'; import { AppInfo } from '../definitions/AppInfo'; -import { App } from '../types'; const getBundledInApp = async (app: App): Promise => { const { bundledIn = [] } = app; diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index b68930eb02d9..98cb59cc79fa 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -4407,6 +4407,7 @@ "There_are_no_personal_access_tokens_created_yet": "There are no Personal Access Tokens created yet.", "There_are_no_users_in_this_role": "There are no users in this role.", "There_is_one_or_more_apps_in_an_invalid_state_Click_here_to_review": "There is one or more apps in an invalid state. Click here to review.", + "There_has_been_an_error_installing_the_app": "There has been an error installing the app", "These_notes_will_be_available_in_the_call_summary": "These notes will be available in the call summary", "This_agent_was_already_selected": "This agent was already selected", "this_app_is_included_with_subscription": "This app is included with __bundleName__ subscription", diff --git a/packages/core-typings/src/Apps.ts b/packages/core-typings/src/Apps.ts index 23bc6d952e9b..65974751bbf1 100644 --- a/packages/core-typings/src/Apps.ts +++ b/packages/core-typings/src/Apps.ts @@ -10,21 +10,49 @@ export type AppScreenshot = { modifiedAt: string; }; -export type PricingPlan = { +export type AppTiers = { + perUnit: boolean; + minimum: number; + maximum: number; + price: number; +}; + +export type AppPricingPlan = { id: string; enabled: boolean; price: number; trialDays: number; strategy: string; isPerSeat: boolean; - tiers?: Tiers[]; + tiers?: AppTiers[]; }; -export type Tiers = { - perUnit: boolean; - minimum: number; - maximum: number; - price: number; +export type AppLicense = { + license: string; + version: number; + expireDate: string; +}; + +export enum AppSubscriptionStatus { + Trialing = 'trialing', + Active = 'active', + Cancelled = 'cancelled', + Cancelling = 'cancelling', + PastDue = 'pastDue', +} + +export type AppSubscriptionInfo = { + typeOf: string; + status: AppSubscriptionStatus; + statusFromBilling: boolean; + isSeatBased: boolean; + seats: number; + maxSeats: number; + license: AppLicense; + startDate: string; + periodEnd: string; + endDate: string; + isSubscribedViaBundle: boolean; }; export type App = { @@ -41,14 +69,19 @@ export type App = { raw: string; rendered: string; }; + detailedChangelog: { + raw: string; + rendered: string; + }; categories: string[]; version: string; price: number; purchaseType: string; - pricingPlans: PricingPlan[]; + pricingPlans: AppPricingPlan[]; iconFileContent: string; installed?: boolean; isEnterpriseOnly?: boolean; + isPurchased?: boolean; isSubscribed: boolean; bundledIn: { bundleId: string; @@ -59,6 +92,7 @@ export type App = { marketplaceVersion: string; latest: App; status?: AppStatus; + subscriptionInfo: AppSubscriptionInfo; licenseValidation?: { errors: { [key: string]: string }; warnings: { [key: string]: string }; @@ -66,4 +100,5 @@ export type App = { marketplace: unknown; modifiedAt: string; permissions: unknown[]; + languages: string[]; }; From 52573cc1f8f82c0929409e887f859578032f04ff Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 15 Jun 2022 12:41:35 -0300 Subject: [PATCH 05/11] Regression: Fix extended sidebar item (#25887) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments Co-authored-by: Douglas Fabris <27704687+dougfabris@users.noreply.github.com> --- apps/meteor/client/sidebar/Item/Extended.tsx | 2 +- .../client/sidebar/RoomList/SideBarItemTemplateWithData.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/meteor/client/sidebar/Item/Extended.tsx b/apps/meteor/client/sidebar/Item/Extended.tsx index 26f7144ff40c..2f66445fe25d 100644 --- a/apps/meteor/client/sidebar/Item/Extended.tsx +++ b/apps/meteor/client/sidebar/Item/Extended.tsx @@ -52,7 +52,7 @@ const Extended: VFC = ({ }; return ( - + {avatar && {avatar}} diff --git a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx index a9cb0476d5b7..8a01dfce72ce 100644 --- a/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx +++ b/apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx @@ -137,7 +137,6 @@ function SideBarItemTemplateWithData({ data-qa='sidebar-item' aria-level={2} unread={highlighted} - threadUnread={threadUnread} selected={selected} href={href} onClick={(): void => !selected && sidebar.toggle()} From c03dd9e6575552559559cb35d12d0aff9d4d065d Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Wed, 15 Jun 2022 12:55:45 -0300 Subject: [PATCH 06/11] [FIX] Kebab menu clicking issue (#25869) Co-authored-by: Tasso Evangelista --- apps/meteor/package.json | 2 +- .../tests/e2e/04-main-elements-render.spec.ts | 2 +- apps/meteor/tests/e2e/pageobjects/FlexTab.ts | 7 -- yarn.lock | 93 ++++++++++++++++++- 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 630192e25146..04d2e49d005e 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -196,7 +196,7 @@ "@rocket.chat/emitter": "~0.31.12", "@rocket.chat/forked-matrix-appservice-bridge": "^4.0.1", "@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.2", - "@rocket.chat/fuselage": "0.31.13", + "@rocket.chat/fuselage": "0.32.0-dev.49", "@rocket.chat/fuselage-hooks": "~0.31.12", "@rocket.chat/fuselage-polyfills": "~0.31.12", "@rocket.chat/fuselage-toastbar": "^0.32.0-dev.22", diff --git a/apps/meteor/tests/e2e/04-main-elements-render.spec.ts b/apps/meteor/tests/e2e/04-main-elements-render.spec.ts index 7f3106d813b5..50e3712e5ae6 100644 --- a/apps/meteor/tests/e2e/04-main-elements-render.spec.ts +++ b/apps/meteor/tests/e2e/04-main-elements-render.spec.ts @@ -180,7 +180,7 @@ test.describe('[Main Elements Render]', function () { }); }); - test.describe.skip('[Members Tab]', () => { + test.describe('[Members Tab]', () => { test.beforeAll(async () => { await flexTab.operateFlexTab('members', true); }); diff --git a/apps/meteor/tests/e2e/pageobjects/FlexTab.ts b/apps/meteor/tests/e2e/pageobjects/FlexTab.ts index 284ce14fb20a..7471e99abfed 100644 --- a/apps/meteor/tests/e2e/pageobjects/FlexTab.ts +++ b/apps/meteor/tests/e2e/pageobjects/FlexTab.ts @@ -342,13 +342,6 @@ export class FlexTab extends BasePage { await locator[tab].click(); - // The button "more" keeps the focus when popover is closed from a click - // on an item, need to click again to change the status to unselected and - // allow the next click to open the popover again - if (more) { - await this.headerMoreActions.click(); - } - if (desiredState) { await expect(locator[panel]).toBeVisible(); } else { diff --git a/yarn.lock b/yarn.lock index 04f22677f49c..131abdfa3578 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3331,6 +3331,19 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-in-js@npm:~0.31.14-dev.2": + version: 0.31.14-dev.2 + resolution: "@rocket.chat/css-in-js@npm:0.31.14-dev.2" + dependencies: + "@emotion/hash": ^0.8.0 + "@rocket.chat/css-supports": ~0.31.14-dev.2 + "@rocket.chat/memo": ~0.31.14-dev.2 + "@rocket.chat/stylis-logical-props-middleware": ~0.31.14-dev.2 + stylis: ~4.0.13 + checksum: 2b8385be25da2a3bf131694e52edbf0fa094449a2ed8e3426eca4149be49dc3b703dea9d257c91d394089a8a9100eb4fad92d86b6aa4049a9f6b89fb3a08d474 + languageName: node + linkType: hard + "@rocket.chat/css-supports@npm:^0.31.13": version: 0.31.13 resolution: "@rocket.chat/css-supports@npm:0.31.13" @@ -3349,6 +3362,15 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-supports@npm:~0.31.14-dev.2": + version: 0.31.14-dev.2 + resolution: "@rocket.chat/css-supports@npm:0.31.14-dev.2" + dependencies: + "@rocket.chat/memo": ~0.31.14-dev.2 + checksum: 0f57cfed074e734c592c0f49cd76f1f33bb373cef889e20e0dc63008b038f67dca5715aee3910c185438ee128c85196ee927f42035285ae2cbc48e2e7389f46d + languageName: node + linkType: hard + "@rocket.chat/ddp-streamer@workspace:ee/apps/ddp-streamer": version: 0.0.0-use.local resolution: "@rocket.chat/ddp-streamer@workspace:ee/apps/ddp-streamer" @@ -3536,13 +3558,27 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:^0.31.13, @rocket.chat/fuselage-tokens@npm:~0.31.11, @rocket.chat/fuselage-tokens@npm:~0.31.12": +"@rocket.chat/fuselage-tokens@npm:^0.31.13, @rocket.chat/fuselage-tokens@npm:~0.31.12": version: 0.31.13 resolution: "@rocket.chat/fuselage-tokens@npm:0.31.13" checksum: 848a800e6bd8c8fc46d473f18152168515e54ecf5b5a51ec4920d5478a28797e9d5bad652708b988305484cf646b3a167d0bff4bf0689bfbc7999f50f4bbf3ef languageName: node linkType: hard +"@rocket.chat/fuselage-tokens@npm:~0.31.11": + version: 0.31.11 + resolution: "@rocket.chat/fuselage-tokens@npm:0.31.11" + checksum: 084ae967f460018002bdf6b0a790c03be520ee76eb7df280bd8efa15d53570f9958692d38edf9e3cc8361d196f2aed66b21ab521737b3008fae7b9d267e3eec7 + languageName: node + linkType: hard + +"@rocket.chat/fuselage-tokens@npm:~0.31.14-dev.2": + version: 0.31.14-dev.2 + resolution: "@rocket.chat/fuselage-tokens@npm:0.31.14-dev.2" + checksum: cda2f7751699ddf14ac77a4902aeeacf8e2863e9a07481f48fbebb4d3ed36ca3dcc655586ca51e8dd7a5e8de613391c32d41961110f8c24a0cbee31fb69dd7a3 + languageName: node + linkType: hard + "@rocket.chat/fuselage-ui-kit@npm:~0.31.14-dev.1": version: 0.31.14-dev.1 resolution: "@rocket.chat/fuselage-ui-kit@npm:0.31.14-dev.1" @@ -3561,7 +3597,29 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage@npm:0.31.13, @rocket.chat/fuselage@npm:^0.31.13": +"@rocket.chat/fuselage@npm:0.32.0-dev.49": + version: 0.32.0-dev.49 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.49" + dependencies: + "@rocket.chat/css-in-js": ~0.31.14-dev.2 + "@rocket.chat/css-supports": ~0.31.14-dev.2 + "@rocket.chat/fuselage-tokens": ~0.31.14-dev.2 + "@rocket.chat/memo": ~0.31.14-dev.2 + "@rocket.chat/styled": ~0.31.14-dev.2 + invariant: ^2.2.4 + react-keyed-flatten-children: ^1.3.0 + peerDependencies: + "@rocket.chat/fuselage-hooks": "*" + "@rocket.chat/fuselage-polyfills": "*" + "@rocket.chat/icons": "*" + react: ^17.0.2 + react-dom: ^17.0.2 + react-virtuoso: 1.2.4 + checksum: 53a45233f48819b6aa91f7e7cf9b4dfa37af219c2ceab73968b8c434dd281ea5233cc2c2c74991ab95f053451c0c9646a77b6381322205f0fddb298a6eb8aec7 + languageName: node + linkType: hard + +"@rocket.chat/fuselage@npm:^0.31.13": version: 0.31.13 resolution: "@rocket.chat/fuselage@npm:0.31.13" dependencies: @@ -3699,6 +3757,13 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/memo@npm:~0.31.14-dev.2": + version: 0.31.14-dev.2 + resolution: "@rocket.chat/memo@npm:0.31.14-dev.2" + checksum: e5892cec4e3137f7f84c51567aa90934c413a0b2deb520385e04646e626147b10a915f486304b4543a386ce83c38b0715d02bfce1d439231f15e60ba6500d220 + languageName: node + linkType: hard + "@rocket.chat/message-parser@npm:next": version: 0.31.14-dev.1 resolution: "@rocket.chat/message-parser@npm:0.31.14-dev.1" @@ -3741,7 +3806,7 @@ __metadata: "@rocket.chat/eslint-config": "workspace:^" "@rocket.chat/forked-matrix-appservice-bridge": ^4.0.1 "@rocket.chat/forked-matrix-bot-sdk": ^0.6.0-beta.2 - "@rocket.chat/fuselage": 0.31.13 + "@rocket.chat/fuselage": 0.32.0-dev.49 "@rocket.chat/fuselage-hooks": ~0.31.12 "@rocket.chat/fuselage-polyfills": ~0.31.12 "@rocket.chat/fuselage-toastbar": ^0.32.0-dev.22 @@ -4109,6 +4174,16 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/styled@npm:~0.31.14-dev.2": + version: 0.31.14-dev.2 + resolution: "@rocket.chat/styled@npm:0.31.14-dev.2" + dependencies: + "@rocket.chat/css-in-js": ~0.31.14-dev.2 + tslib: ^2.3.1 + checksum: 792890b3c2f93e7d2f39065bb4b614bcf9525842566cabda60326f148ad81dd64f40ddaf5770a2cda01c847ee2ba6681ebf6e0403f7f1d11c879ee80dc6049c1 + languageName: node + linkType: hard + "@rocket.chat/stylis-logical-props-middleware@npm:^0.31.13": version: 0.31.13 resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.13" @@ -4133,6 +4208,18 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/stylis-logical-props-middleware@npm:~0.31.14-dev.2": + version: 0.31.14-dev.2 + resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.14-dev.2" + dependencies: + "@rocket.chat/css-supports": ~0.31.14-dev.2 + tslib: ^2.3.1 + peerDependencies: + stylis: 4.0.10 + checksum: e3ed7b3bdab5f330045d1fc4f7d57e3086ba44e0dbdfd8b98c1f315a1662256815e2383f26c4a32838afeafa4d6aae5d40f8fca69b265655b3264d43f9af61b2 + languageName: node + linkType: hard + "@rocket.chat/ui-contexts@workspace:^, @rocket.chat/ui-contexts@workspace:packages/ui-contexts": version: 0.0.0-use.local resolution: "@rocket.chat/ui-contexts@workspace:packages/ui-contexts" From 04564dbc1b71804e33a56ae735b41c3103c82b17 Mon Sep 17 00:00:00 2001 From: Felipe <84182706+felipe-rod123@users.noreply.github.com> Date: Wed, 15 Jun 2022 14:26:49 -0300 Subject: [PATCH 07/11] Chore: convert apps/meteor/app/api/server/lib/ files to TS (#25840) Co-authored-by: Guilherme Gazzo Co-authored-by: Guilherme Gazzo --- apps/meteor/app/api/server/api.d.ts | 8 +- .../app/api/server/helpers/parseJsonQuery.ts | 2 +- .../meteor/app/api/server/lib/emoji-custom.js | 20 ----- .../meteor/app/api/server/lib/emoji-custom.ts | 34 +++++++ .../app/api/server/lib/getUploadFormData.js | 36 -------- .../app/api/server/lib/getUploadFormData.ts | 90 +++++++++++++++++++ .../server/lib/{messages.js => messages.ts} | 72 +++++++++++++-- .../app/api/server/lib/{rooms.js => rooms.ts} | 62 ++++++++++--- .../app/api/server/lib/{users.js => users.ts} | 43 +++++++-- apps/meteor/app/api/server/v1/assets.js | 6 +- apps/meteor/app/api/server/v1/emoji-custom.ts | 43 +++++---- apps/meteor/app/api/server/v1/rooms.js | 11 ++- apps/meteor/app/api/server/v1/users.js | 9 +- .../app/apps/server/bridges/listeners.js | 1 + .../app/apps/server/communication/rest.js | 22 +++-- .../client/{index.js => index.ts} | 0 .../client/lib/{streamer.js => streamer.ts} | 0 apps/meteor/app/authorization/index.js | 2 +- ...anDeleteMessage.js => canDeleteMessage.ts} | 11 ++- .../livechat/imports/server/rest/upload.js | 11 ++- apps/meteor/package.json | 1 + yarn.lock | 10 +++ 22 files changed, 355 insertions(+), 139 deletions(-) delete mode 100644 apps/meteor/app/api/server/lib/emoji-custom.js create mode 100644 apps/meteor/app/api/server/lib/emoji-custom.ts delete mode 100644 apps/meteor/app/api/server/lib/getUploadFormData.js create mode 100644 apps/meteor/app/api/server/lib/getUploadFormData.ts rename apps/meteor/app/api/server/lib/{messages.js => messages.ts} (66%) rename apps/meteor/app/api/server/lib/{rooms.js => rooms.ts} (69%) rename apps/meteor/app/api/server/lib/{users.js => users.ts} (66%) rename apps/meteor/app/authorization/client/{index.js => index.ts} (100%) rename apps/meteor/app/authorization/client/lib/{streamer.js => streamer.ts} (100%) rename apps/meteor/app/authorization/server/functions/{canDeleteMessage.js => canDeleteMessage.ts} (73%) diff --git a/apps/meteor/app/api/server/api.d.ts b/apps/meteor/app/api/server/api.d.ts index e8a8634e0615..a1dd63713747 100644 --- a/apps/meteor/app/api/server/api.d.ts +++ b/apps/meteor/app/api/server/api.d.ts @@ -9,6 +9,7 @@ import type { } from '@rocket.chat/rest-typings'; import type { IUser, IMethodConnection, IRoom } from '@rocket.chat/core-typings'; import type { ValidateFunction } from 'ajv'; +import type { Request } from 'express'; import { ITwoFactorOptions } from '../../2fa/server/code'; @@ -70,13 +71,6 @@ type Options = ( validateParams?: ValidateFunction; }; -type Request = { - method: 'GET' | 'POST' | 'PUT' | 'DELETE'; - url: string; - headers: Record; - body: any; -}; - type PartialThis = { readonly request: Request & { query: Record }; }; diff --git a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts index 7e3592e765a0..4710aac5bc25 100644 --- a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts +++ b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts @@ -70,7 +70,7 @@ API.helperMethods.set( if (typeof fields === 'object') { let nonSelectableFields = Object.keys(API.v1.defaultFieldsToExclude); if (this.request.route.includes('/v1/users.')) { - const getFields = () => + const getFields = (): string[] => Object.keys( hasPermission(this.userId, 'view-full-other-user-info') ? API.v1.limitedUserFieldsToExcludeIfIsPrivilegedUser diff --git a/apps/meteor/app/api/server/lib/emoji-custom.js b/apps/meteor/app/api/server/lib/emoji-custom.js deleted file mode 100644 index 1d7dde270664..000000000000 --- a/apps/meteor/app/api/server/lib/emoji-custom.js +++ /dev/null @@ -1,20 +0,0 @@ -import { EmojiCustom } from '../../../models/server/raw'; - -export async function findEmojisCustom({ query = {}, pagination: { offset, count, sort } }) { - const cursor = EmojiCustom.find(query, { - sort: sort || { name: 1 }, - skip: offset, - limit: count, - }); - - const total = await cursor.count(); - - const emojis = await cursor.toArray(); - - return { - emojis, - count: emojis.length, - offset, - total, - }; -} diff --git a/apps/meteor/app/api/server/lib/emoji-custom.ts b/apps/meteor/app/api/server/lib/emoji-custom.ts new file mode 100644 index 000000000000..2c3f660c67f1 --- /dev/null +++ b/apps/meteor/app/api/server/lib/emoji-custom.ts @@ -0,0 +1,34 @@ +import { IEmojiCustom, ILivechatDepartmentRecord } from '@rocket.chat/core-typings'; +import { FilterQuery, SortOptionObject } from 'mongodb'; + +import { EmojiCustom } from '../../../models/server/raw'; + +export async function findEmojisCustom({ + query = {}, + pagination: { offset, count, sort }, +}: { + query: FilterQuery; + pagination: { offset: number; count: number; sort: SortOptionObject }; +}): Promise<{ + emojis: IEmojiCustom[]; + count: number; + offset: any; + total: number; +}> { + const cursor = EmojiCustom.find(query, { + sort: sort || { name: 1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const emojis = await cursor.toArray(); + + return { + emojis, + count: emojis.length, + offset, + total, + }; +} diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.js b/apps/meteor/app/api/server/lib/getUploadFormData.js deleted file mode 100644 index 553ebe0a27ba..000000000000 --- a/apps/meteor/app/api/server/lib/getUploadFormData.js +++ /dev/null @@ -1,36 +0,0 @@ -import busboy from 'busboy'; - -export const getUploadFormData = async ({ request }) => - new Promise((resolve, reject) => { - const bb = busboy({ headers: request.headers, defParamCharset: 'utf8' }); - - const fields = {}; - - bb.on('file', (fieldname, file, { filename, encoding, mimeType: mimetype }) => { - const fileData = []; - - file.on('data', (data) => fileData.push(data)); - - file.on('end', () => { - if (fields.hasOwnProperty(fieldname)) { - return reject('Just 1 file is allowed'); - } - - fields[fieldname] = { - file, - filename, - encoding, - mimetype, - fileBuffer: Buffer.concat(fileData), - }; - }); - }); - - bb.on('field', (fieldname, value) => { - fields[fieldname] = value; - }); - - bb.on('finish', () => resolve(fields)); - - request.pipe(bb); - }); diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.ts b/apps/meteor/app/api/server/lib/getUploadFormData.ts new file mode 100644 index 000000000000..c9cf0263c5d8 --- /dev/null +++ b/apps/meteor/app/api/server/lib/getUploadFormData.ts @@ -0,0 +1,90 @@ +import { Readable } from 'stream'; + +import { Meteor } from 'meteor/meteor'; +import type { Request } from 'express'; +import busboy from 'busboy'; +import { ValidateFunction } from 'ajv'; + +type UploadResult = { + file: Readable; + filename: string; + encoding: string; + mimetype: string; + fileBuffer: Buffer; +}; + +export const getUploadFormData = async >( + { request }: { request: Request }, + options: { + field?: T; + validate?: V; + } = {}, +): Promise< + [ + UploadResult, + K extends unknown + ? { + [k: string]: string; + } + : K, + T, + ] +> => + new Promise((resolve, reject) => { + const bb = busboy({ headers: request.headers, defParamCharset: 'utf8' }); + const fields: { [K: string]: string } = Object.create(null); + + let uploadedFile: UploadResult | undefined; + + let assetName: T | undefined; + + bb.on( + 'file', + ( + fieldname: string, + file: Readable, + { filename, encoding, mimeType: mimetype }: { filename: string; encoding: string; mimeType: string }, + ) => { + const fileData: Uint8Array[] = []; + + file.on('data', (data: any) => fileData.push(data)); + + file.on('end', () => { + if (uploadedFile) { + return reject('Just 1 file is allowed'); + } + if (options.field && fieldname !== options.field) { + return reject(new Meteor.Error('invalid-field')); + } + uploadedFile = { + file, + filename, + encoding, + mimetype, + fileBuffer: Buffer.concat(fileData), + }; + + assetName = fieldname as T; + }); + }, + ); + + bb.on('field', (fieldname, value) => { + fields[fieldname] = value; + }); + + bb.on('finish', () => { + if (!uploadedFile || !assetName) { + return reject('No file uploaded'); + } + if (options.validate === undefined) { + return resolve([uploadedFile, fields, assetName]); + } + if (!options.validate(fields)) { + return reject(`Invalid fields${options.validate.errors?.join(', ')}`); + } + return resolve([uploadedFile, fields, assetName]); + }); + + request.pipe(bb); + }); diff --git a/apps/meteor/app/api/server/lib/messages.js b/apps/meteor/app/api/server/lib/messages.ts similarity index 66% rename from apps/meteor/app/api/server/lib/messages.js rename to apps/meteor/app/api/server/lib/messages.ts index d06eef7f0671..194bdabdd274 100644 --- a/apps/meteor/app/api/server/lib/messages.js +++ b/apps/meteor/app/api/server/lib/messages.ts @@ -1,13 +1,28 @@ +import { IMessage, IUser } from '@rocket.chat/core-typings'; + import { canAccessRoomAsync } from '../../../authorization/server/functions/canAccessRoom'; import { Rooms, Messages, Users } from '../../../models/server/raw'; import { getValue } from '../../../settings/server/raw'; -export async function findMentionedMessages({ uid, roomId, pagination: { offset, count, sort } }) { +export async function findMentionedMessages({ + uid, + roomId, + pagination: { offset, count, sort }, +}: { + uid: string; + roomId: string; + pagination: { offset: number; count: number; sort: [string, number][] }; +}): Promise<{ + messages: IMessage[]; + count: number; + offset: number; + total: number; +}> { const room = await Rooms.findOneById(roomId); if (!(await canAccessRoomAsync(room, { _id: uid }))) { throw new Error('error-not-allowed'); } - const user = await Users.findOneById(uid, { fields: { username: 1 } }); + const user: IUser | null = await Users.findOneById(uid, { fields: { username: 1 } }); if (!user) { throw new Error('invalid-user'); } @@ -30,7 +45,20 @@ export async function findMentionedMessages({ uid, roomId, pagination: { offset, }; } -export async function findStarredMessages({ uid, roomId, pagination: { offset, count, sort } }) { +export async function findStarredMessages({ + uid, + roomId, + pagination: { offset, count, sort }, +}: { + uid: string; + roomId: string; + pagination: { offset: number; count: number; sort: [string, number][] }; +}): Promise<{ + messages: IMessage[]; + count: number; + offset: any; + total: number; +}> { const room = await Rooms.findOneById(roomId); if (!(await canAccessRoomAsync(room, { _id: uid }))) { throw new Error('error-not-allowed'); @@ -58,7 +86,7 @@ export async function findStarredMessages({ uid, roomId, pagination: { offset, c }; } -export async function findSnippetedMessageById({ uid, messageId }) { +export async function findSnippetedMessageById({ uid, messageId }: { uid: string; messageId: string }): Promise { if (!(await getValue('Message_AllowSnippeting'))) { throw new Error('error-not-allowed'); } @@ -83,12 +111,23 @@ export async function findSnippetedMessageById({ uid, messageId }) { throw new Error('error-not-allowed'); } - return { - message: snippet, - }; + return snippet; } -export async function findSnippetedMessages({ uid, roomId, pagination: { offset, count, sort } }) { +export async function findSnippetedMessages({ + uid, + roomId, + pagination: { offset, count, sort }, +}: { + uid: string; + roomId: string; + pagination: { offset: number; count: number; sort: [string, number][] }; +}): Promise<{ + messages: IMessage[]; + count: number; + offset: number; + total: number; +}> { if (!(await getValue('Message_AllowSnippeting'))) { throw new Error('error-not-allowed'); } @@ -116,7 +155,22 @@ export async function findSnippetedMessages({ uid, roomId, pagination: { offset, }; } -export async function findDiscussionsFromRoom({ uid, roomId, text, pagination: { offset, count, sort } }) { +export async function findDiscussionsFromRoom({ + uid, + roomId, + text, + pagination: { offset, count, sort }, +}: { + uid: string; + roomId: string; + text: string; + pagination: { offset: number; count: number; sort: [string, number][] }; +}): Promise<{ + messages: IMessage[]; + count: number; + offset: number; + total: number; +}> { const room = await Rooms.findOneById(roomId); if (!(await canAccessRoomAsync(room, { _id: uid }))) { diff --git a/apps/meteor/app/api/server/lib/rooms.js b/apps/meteor/app/api/server/lib/rooms.ts similarity index 69% rename from apps/meteor/app/api/server/lib/rooms.js rename to apps/meteor/app/api/server/lib/rooms.ts index 0e402dec3ffe..5ffd4798bb78 100644 --- a/apps/meteor/app/api/server/lib/rooms.js +++ b/apps/meteor/app/api/server/lib/rooms.ts @@ -1,15 +1,32 @@ +import { IRoom, ISubscription } from '@rocket.chat/core-typings'; + import { hasPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { Rooms } from '../../../models/server/raw'; import { Subscriptions } from '../../../models/server'; import { adminFields } from '../../../../lib/rooms/adminFields'; -export async function findAdminRooms({ uid, filter, types = [], pagination: { offset, count, sort } }) { +export async function findAdminRooms({ + uid, + filter, + types = [], + pagination: { offset, count, sort }, +}: { + uid: string; + filter: string; + types: string[]; + pagination: { offset: number; count: number; sort: [string, number][] }; +}): Promise<{ + rooms: IRoom[]; + count: number; + offset: number; + total: number; +}> { if (!(await hasPermissionAsync(uid, 'view-room-administration'))) { throw new Error('error-not-authorized'); } - const name = filter && filter.trim(); - const discussion = types && types.includes('discussions'); - const includeTeams = types && types.includes('teams'); + const name = filter?.trim(); + const discussion = types?.includes('discussions'); + const includeTeams = types?.includes('teams'); const showOnlyTeams = types.length === 1 && types.includes('teams'); const typesToRemove = ['discussions', 'teams']; const showTypes = Array.isArray(types) ? types.filter((type) => !typesToRemove.includes(type)) : []; @@ -41,7 +58,7 @@ export async function findAdminRooms({ uid, filter, types = [], pagination: { of }; } -export async function findAdminRoom({ uid, rid }) { +export async function findAdminRoom({ uid, rid }: { uid: string; rid: string }): Promise { if (!(await hasPermissionAsync(uid, 'view-room-administration'))) { throw new Error('error-not-authorized'); } @@ -49,7 +66,9 @@ export async function findAdminRoom({ uid, rid }) { return Rooms.findOneById(rid, { fields: adminFields }); } -export async function findChannelAndPrivateAutocomplete({ uid, selector }) { +export async function findChannelAndPrivateAutocomplete({ uid, selector }: { uid: string; selector: { name: string } }): Promise<{ + items: IRoom[]; +}> { const options = { fields: { _id: 1, @@ -66,7 +85,7 @@ export async function findChannelAndPrivateAutocomplete({ uid, selector }) { const userRoomsIds = Subscriptions.cachedFindByUserId(uid, { fields: { rid: 1 } }) .fetch() - .map((item) => item.rid); + .map((item: Pick) => item.rid); const rooms = await Rooms.findRoomsWithoutDiscussionsByRoomIds(selector.name, userRoomsIds, options).toArray(); @@ -75,7 +94,9 @@ export async function findChannelAndPrivateAutocomplete({ uid, selector }) { }; } -export async function findAdminRoomsAutocomplete({ uid, selector }) { +export async function findAdminRoomsAutocomplete({ uid, selector }: { uid: string; selector: { name: string } }): Promise<{ + items: IRoom[]; +}> { if (!(await hasAtLeastOnePermissionAsync(uid, ['view-room-administration', 'can-audit']))) { throw new Error('error-not-authorized'); } @@ -100,10 +121,21 @@ export async function findAdminRoomsAutocomplete({ uid, selector }) { }; } -export async function findChannelAndPrivateAutocompleteWithPagination({ uid, selector, pagination: { offset, count, sort } }) { +export async function findChannelAndPrivateAutocompleteWithPagination({ + uid, + selector, + pagination: { offset, count, sort }, +}: { + uid: string; + selector: { name: string }; + pagination: { offset: number; count: number; sort: [string, number][] }; +}): Promise<{ + items: IRoom[]; + total: number; +}> { const userRoomsIds = Subscriptions.cachedFindByUserId(uid, { fields: { rid: 1 } }) .fetch() - .map((item) => item.rid); + .map((item: Pick) => item.rid); const options = { fields: { @@ -129,7 +161,9 @@ export async function findChannelAndPrivateAutocompleteWithPagination({ uid, sel }; } -export async function findRoomsAvailableForTeams({ uid, name }) { +export async function findRoomsAvailableForTeams({ uid, name }: { uid: string; name: string }): Promise<{ + items: IRoom[]; +}> { const options = { fields: { _id: 1, @@ -144,9 +178,9 @@ export async function findRoomsAvailableForTeams({ uid, name }) { }, }; - const userRooms = Subscriptions.findByUserIdAndRoles(uid, ['owner'], { fields: { rid: 1 } }) - .fetch() - .map((item) => item.rid); + const userRooms = ( + Subscriptions.findByUserIdAndRoles(uid, ['owner'], { fields: { rid: 1 } }).fetch() as Pick[] + ).map((item) => item.rid); const rooms = await Rooms.findChannelAndGroupListWithoutTeamsByNameStartingByOwner(uid, name, userRooms, options).toArray(); diff --git a/apps/meteor/app/api/server/lib/users.js b/apps/meteor/app/api/server/lib/users.ts similarity index 66% rename from apps/meteor/app/api/server/lib/users.js rename to apps/meteor/app/api/server/lib/users.ts index 1e7eb9a7a242..8ff1737cc692 100644 --- a/apps/meteor/app/api/server/lib/users.js +++ b/apps/meteor/app/api/server/lib/users.ts @@ -1,9 +1,23 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; +import { ILivechatDepartmentRecord, IUser } from '@rocket.chat/core-typings'; +import { FilterQuery } from 'mongodb'; import { Users } from '../../../models/server/raw'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -export async function findUsersToAutocomplete({ uid, selector }) { +export async function findUsersToAutocomplete({ + uid, + selector, +}: { + uid: string; + selector: { + exceptions: string[]; + conditions: FilterQuery; + term: string; + }; +}): Promise<{ + items: IUser[]; +}> { if (!(await hasPermissionAsync(uid, 'view-outside-room'))) { return { items: [] }; } @@ -39,8 +53,8 @@ export async function findUsersToAutocomplete({ uid, selector }) { * Returns a new query object with the inclusive fields only * @param {Object} query search query for matching rows */ -export function getInclusiveFields(query) { - const newQuery = {}; +export function getInclusiveFields(query: { [k: string]: 1 }): {} { + const newQuery = Object.create(null); for (const [key, value] of Object.entries(query)) { if (value === 1) { @@ -55,7 +69,16 @@ export function getInclusiveFields(query) { * get the default fields if **fields** are empty (`{}`) or `undefined`/`null` * @param {Object|null|undefined} fields the fields from parsed jsonQuery */ -export function getNonEmptyFields(fields) { +export function getNonEmptyFields(fields: {}): { + name: number; + username: number; + emails: number; + roles: number; + status: number; + active: number; + avatarETag: number; + lastLogin: number; +} { const defaultFields = { name: 1, username: 1, @@ -74,11 +97,21 @@ export function getNonEmptyFields(fields) { return { ...defaultFields, ...fields }; } +const _defaultQuery = { + $or: [ + { 'emails.address': { $regex: '', $options: 'i' } }, + { username: { $regex: '', $options: 'i' } }, + { name: { $regex: '', $options: 'i' } }, + ], +}; + /** * get the default query if **query** is empty (`{}`) or `undefined`/`null` * @param {Object|null|undefined} query the query from parsed jsonQuery */ -export function getNonEmptyQuery(query) { + +type Query = { [k: string]: unknown }; +export function getNonEmptyQuery(query: Query): typeof _defaultQuery | (typeof _defaultQuery & Query) { const defaultQuery = { $or: [ { 'emails.address': { $regex: '', $options: 'i' } }, diff --git a/apps/meteor/app/api/server/v1/assets.js b/apps/meteor/app/api/server/v1/assets.js index c232d3c0ff2c..7138d565034b 100644 --- a/apps/meteor/app/api/server/v1/assets.js +++ b/apps/meteor/app/api/server/v1/assets.js @@ -9,7 +9,7 @@ API.v1.addRoute( { authRequired: true }, { post() { - const { refreshAllClients, ...files } = Promise.await( + const [asset, { refreshAllClients }, assetName] = Promise.await( getUploadFormData({ request: this.request, }), @@ -17,16 +17,12 @@ API.v1.addRoute( const assetsKeys = Object.keys(RocketChatAssets.assets); - const [assetName] = Object.keys(files); - const isValidAsset = assetsKeys.includes(assetName); if (!isValidAsset) { throw new Meteor.Error('error-invalid-asset', 'Invalid asset'); } Meteor.runAsUser(this.userId, () => { - const { [assetName]: asset } = files; - Meteor.call('setAsset', asset.fileBuffer, asset.mimetype, assetName); if (refreshAllClients) { Meteor.call('refreshClients'); diff --git a/apps/meteor/app/api/server/v1/emoji-custom.ts b/apps/meteor/app/api/server/v1/emoji-custom.ts index fe16e3ed2392..df66f686edfd 100644 --- a/apps/meteor/app/api/server/v1/emoji-custom.ts +++ b/apps/meteor/app/api/server/v1/emoji-custom.ts @@ -67,13 +67,12 @@ API.v1.addRoute( { authRequired: true }, { async post() { - const { emoji, ...fields } = await getUploadFormData({ - request: this.request, - }); - - if (!emoji) { - throw new Meteor.Error('invalid-field'); - } + const [emoji, fields] = await getUploadFormData( + { + request: this.request, + }, + { field: 'emoji' }, + ); const isUploadable = await Media.isImage(emoji.fileBuffer); if (!isUploadable) { @@ -83,11 +82,16 @@ API.v1.addRoute( const [, extension] = emoji.mimetype.split('/'); fields.extension = extension; - fields.newFile = true; - fields.aliases = fields.aliases || ''; - - Meteor.call('insertOrUpdateEmoji', fields); - Meteor.call('uploadEmojiCustom', emoji.fileBuffer, emoji.mimetype, fields); + Meteor.call('insertOrUpdateEmoji', { + ...fields, + newFile: true, + aliases: fields.aliases || '', + }); + Meteor.call('uploadEmojiCustom', emoji.fileBuffer, emoji.mimetype, { + ...fields, + newFile: true, + aliases: fields.aliases || '', + }); return API.v1.success(); }, @@ -99,9 +103,12 @@ API.v1.addRoute( { authRequired: true }, { async post() { - const { emoji, ...fields } = await getUploadFormData({ - request: this.request, - }); + const [emoji, fields] = await getUploadFormData( + { + request: this.request, + }, + { field: 'emoji' }, + ); if (!fields._id) { throw new Meteor.Error('The required "_id" query param is missing.'); @@ -115,7 +122,7 @@ API.v1.addRoute( fields.previousName = emojiToUpdate.name; fields.previousExtension = emojiToUpdate.extension; fields.aliases = fields.aliases || ''; - fields.newFile = Boolean(emoji?.fileBuffer.length); + const newFile = Boolean(emoji?.fileBuffer.length); if (fields.newFile) { const isUploadable = Promise.await(Media.isImage(emoji.fileBuffer)); @@ -129,9 +136,9 @@ API.v1.addRoute( fields.extension = emojiToUpdate.extension; } - Meteor.call('insertOrUpdateEmoji', fields); + Meteor.call('insertOrUpdateEmoji', { ...fields, newFile }); if (fields.newFile) { - Meteor.call('uploadEmojiCustom', emoji.fileBuffer, emoji.mimetype, fields); + Meteor.call('uploadEmojiCustom', emoji.fileBuffer, emoji.mimetype, { ...fields, newFile }); } return API.v1.success(); }, diff --git a/apps/meteor/app/api/server/v1/rooms.js b/apps/meteor/app/api/server/v1/rooms.js index 5d722a6e9fc4..3efbf9f00116 100644 --- a/apps/meteor/app/api/server/v1/rooms.js +++ b/apps/meteor/app/api/server/v1/rooms.js @@ -85,10 +85,13 @@ API.v1.addRoute( return API.v1.unauthorized(); } - const { file, ...fields } = Promise.await( - getUploadFormData({ - request: this.request, - }), + const [file, fields] = Promise.await( + getUploadFormData( + { + request: this.request, + }, + { field: 'file' }, + ), ); if (!file) { diff --git a/apps/meteor/app/api/server/v1/users.js b/apps/meteor/app/api/server/v1/users.js index d57d477a4cf3..094570518e2d 100644 --- a/apps/meteor/app/api/server/v1/users.js +++ b/apps/meteor/app/api/server/v1/users.js @@ -462,9 +462,12 @@ API.v1.addRoute( return API.v1.success(); } - const { image, ...fields } = await getUploadFormData({ - request: this.request, - }); + const [image, fields] = await getUploadFormData( + { + request: this.request, + }, + { field: 'image' }, + ); if (!image) { return API.v1.failure("The 'image' param is required"); diff --git a/apps/meteor/app/apps/server/bridges/listeners.js b/apps/meteor/app/apps/server/bridges/listeners.js index 57e0a9b63bba..ced6e1aea31b 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.js +++ b/apps/meteor/app/apps/server/bridges/listeners.js @@ -7,6 +7,7 @@ export class AppListenerBridge { } async handleEvent(event, ...payload) { + // eslint-disable-next-line complexity const method = (() => { switch (event) { case AppInterface.IPreMessageSentPrevent: diff --git a/apps/meteor/app/apps/server/communication/rest.js b/apps/meteor/app/apps/server/communication/rest.js index 2bc64e1f8be5..0b7122e37521 100644 --- a/apps/meteor/app/apps/server/communication/rest.js +++ b/apps/meteor/app/apps/server/communication/rest.js @@ -213,10 +213,13 @@ export class AppsRestApi { return API.v1.failure({ error: 'Direct installation of an App is disabled.' }); } - const formData = await getUploadFormData({ - request: this.request, - }); - buff = formData?.app?.fileBuffer; + const [app, formData] = await getUploadFormData( + { + request: this.request, + }, + { field: 'app' }, + ); + buff = app?.fileBuffer; permissionsGranted = (() => { try { const permissions = JSON.parse(formData?.permissions || ''); @@ -462,10 +465,13 @@ export class AppsRestApi { return API.v1.failure({ error: 'Direct updating of an App is disabled.' }); } - const formData = await getUploadFormData({ - request: this.request, - }); - buff = formData?.app?.fileBuffer; + const [app, formData] = await getUploadFormData( + { + request: this.request, + }, + { field: 'app' }, + ); + buff = app?.fileBuffer; permissionsGranted = (() => { try { const permissions = JSON.parse(formData?.permissions || ''); diff --git a/apps/meteor/app/authorization/client/index.js b/apps/meteor/app/authorization/client/index.ts similarity index 100% rename from apps/meteor/app/authorization/client/index.js rename to apps/meteor/app/authorization/client/index.ts diff --git a/apps/meteor/app/authorization/client/lib/streamer.js b/apps/meteor/app/authorization/client/lib/streamer.ts similarity index 100% rename from apps/meteor/app/authorization/client/lib/streamer.js rename to apps/meteor/app/authorization/client/lib/streamer.ts diff --git a/apps/meteor/app/authorization/index.js b/apps/meteor/app/authorization/index.js index a67eca871efb..c20f7ea60706 100644 --- a/apps/meteor/app/authorization/index.js +++ b/apps/meteor/app/authorization/index.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; if (Meteor.isClient) { - module.exports = require('./client/index.js'); + module.exports = require('./client/'); } if (Meteor.isServer) { module.exports = require('./server/index.js'); diff --git a/apps/meteor/app/authorization/server/functions/canDeleteMessage.js b/apps/meteor/app/authorization/server/functions/canDeleteMessage.ts similarity index 73% rename from apps/meteor/app/authorization/server/functions/canDeleteMessage.js rename to apps/meteor/app/authorization/server/functions/canDeleteMessage.ts index 1e5f2a577eef..36d7b30394c9 100644 --- a/apps/meteor/app/authorization/server/functions/canDeleteMessage.js +++ b/apps/meteor/app/authorization/server/functions/canDeleteMessage.ts @@ -1,13 +1,15 @@ +import { IUser } from '@rocket.chat/core-typings'; + import { hasPermissionAsync } from './hasPermission'; import { getValue } from '../../../settings/server/raw'; -import { Rooms } from '../../../models'; +import { Rooms } from '../../../models/server'; -const elapsedTime = (ts) => { +const elapsedTime = (ts: number): number => { const dif = Date.now() - ts; return Math.round(dif / 1000 / 60); }; -export const canDeleteMessageAsync = async (uid, { u, rid, ts }) => { +export const canDeleteMessageAsync = async (uid: string, { u, rid, ts }: { u: IUser; rid: string; ts: number }): Promise => { const forceDelete = await hasPermissionAsync(uid, 'force-delete-message', rid); if (forceDelete) { @@ -47,4 +49,5 @@ export const canDeleteMessageAsync = async (uid, { u, rid, ts }) => { return true; }; -export const canDeleteMessage = (uid, { u, rid, ts }) => Promise.await(canDeleteMessageAsync(uid, { u, rid, ts })); +export const canDeleteMessage = (uid: string, { u, rid, ts }: { u: IUser; rid: string; ts: number }): boolean => + Promise.await(canDeleteMessageAsync(uid, { u, rid, ts })); diff --git a/apps/meteor/app/livechat/imports/server/rest/upload.js b/apps/meteor/app/livechat/imports/server/rest/upload.js index 8547b94645f1..f6d4f1485a32 100644 --- a/apps/meteor/app/livechat/imports/server/rest/upload.js +++ b/apps/meteor/app/livechat/imports/server/rest/upload.js @@ -36,10 +36,13 @@ API.v1.addRoute('livechat/upload/:rid', { return API.v1.unauthorized(); } - const { file, ...fields } = Promise.await( - getUploadFormData({ - request: this.request, - }), + const [file, fields] = Promise.await( + getUploadFormData( + { + request: this.request, + }, + { field: 'file' }, + ), ); if (!fileUploadIsValidContentType(file.mimetype)) { diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 04d2e49d005e..b4490100493c 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -84,6 +84,7 @@ "@types/bad-words": "^3.0.1", "@types/bcrypt": "^5.0.0", "@types/body-parser": "^1.19.0", + "@types/busboy": "^1.5.0", "@types/chai": "^4.2.22", "@types/chai-datetime": "0.0.37", "@types/chai-dom": "0.0.12", diff --git a/yarn.lock b/yarn.lock index 131abdfa3578..06f4fc2cfcfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3839,6 +3839,7 @@ __metadata: "@types/bad-words": ^3.0.1 "@types/bcrypt": ^5.0.0 "@types/body-parser": ^1.19.0 + "@types/busboy": ^1.5.0 "@types/chai": ^4.2.22 "@types/chai-datetime": 0.0.37 "@types/chai-dom": 0.0.12 @@ -5829,6 +5830,15 @@ __metadata: languageName: node linkType: hard +"@types/busboy@npm:^1.5.0": + version: 1.5.0 + resolution: "@types/busboy@npm:1.5.0" + dependencies: + "@types/node": "*" + checksum: ffa7bf25c0395f6927526b7d97e70cd2df789e4ca0d231e41855fb08542fa236891ce457d83cc50cac6e5cef6be092ab80597070dcf1413f736462690a23e987 + languageName: node + linkType: hard + "@types/chai-datetime@npm:0.0.37": version: 0.0.37 resolution: "@types/chai-datetime@npm:0.0.37" From 38d3a81c501b13da7ef6fa4a99d317a36dad6856 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 15 Jun 2022 15:36:28 -0300 Subject: [PATCH 08/11] Chore: Fix correct unit test to api files (#25870) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- apps/meteor/.mocharc.api.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/meteor/.mocharc.api.js b/apps/meteor/.mocharc.api.js index 934fd1509ae1..c7994105d68a 100644 --- a/apps/meteor/.mocharc.api.js +++ b/apps/meteor/.mocharc.api.js @@ -9,5 +9,10 @@ module.exports = { timeout: 10000, bail: true, file: 'tests/end-to-end/teardown.js', - spec: ['tests/unit/app/api/server/v1/*.spec.ts', 'tests/end-to-end/api/*.js', 'tests/end-to-end/api/*.ts', 'tests/end-to-end/apps/*.js'], + spec: [ + 'tests/unit/app/api/server/v1/**/*.spec.ts', + 'tests/end-to-end/api/*.js', + 'tests/end-to-end/api/*.ts', + 'tests/end-to-end/apps/*.js', + ], }; From 45b6640f41092c068210f95ccab0ee7d66885029 Mon Sep 17 00:00:00 2001 From: souzaramon Date: Wed, 15 Jun 2022 17:04:47 -0300 Subject: [PATCH 09/11] Chore: create a e2e test guideline (#25884) ## Proposed changes (including videos or screenshots) - Create a e2e guideline --- apps/meteor/tests/e2e/07-emoji.spec.ts | 2 +- apps/meteor/tests/e2e/README.md | 42 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 apps/meteor/tests/e2e/README.md diff --git a/apps/meteor/tests/e2e/07-emoji.spec.ts b/apps/meteor/tests/e2e/07-emoji.spec.ts index da2bdc52cc17..473b4c2f640e 100644 --- a/apps/meteor/tests/e2e/07-emoji.spec.ts +++ b/apps/meteor/tests/e2e/07-emoji.spec.ts @@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test'; import { SideNav, MainContent, LoginPage } from './pageobjects'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; -test.describe('[Emoji]', function () { +test.describe('[Emoji]', () => { let loginPage: LoginPage; let mainContent: MainContent; let sideNav: SideNav; diff --git a/apps/meteor/tests/e2e/README.md b/apps/meteor/tests/e2e/README.md new file mode 100644 index 000000000000..bd4f89a8d209 --- /dev/null +++ b/apps/meteor/tests/e2e/README.md @@ -0,0 +1,42 @@ +# E2E Testing with playwright + +## Running tests +The application must be started with `TEST_MODE=true` + +```sh +$ TEST_MODE=true yarn dev +``` + +Then we can run a single suite with + +```sh +$ yarn test:playwright ./tests/e2e/01-forgot-password.spec.ts +``` + +Or all the tests with + +```sh +$ yarn test:playwright +``` + +We can also provide some env vars to `test:playwright` script: +- `BASE_URL=` Run the tests to the given url +- `PWDEBUG=1` Controll the test execution + +## Current limitations +- 00-wizard.spec.ts will only pass if the database is empty +- There are some dependencies between tests, so we can't run the suites in parallel + +## Page Objects +- Any locator name must start with of one the following prefixes: `btn`, `link`, `input`, `select`, `checkbox`, `text` +- Any action name should starts with the prefix `do` + +## Important links +- [playwright docs](https://playwright.dev/docs/intro) + +## Assertions +Checking if a element is visible + +```ts + await expect(anyElement).toBeVisible(); + ``` From 750f0ef5b146ed6e42ac1c922badf9c21b10c3ff Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Thu, 16 Jun 2022 09:25:21 +0530 Subject: [PATCH 10/11] Regression: TOTP Modal with new rest api package (#25893) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com> --- apps/meteor/app/utils/client/lib/RestApiClient.ts | 13 +++++++++---- packages/api-client/src/index.ts | 5 +++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/meteor/app/utils/client/lib/RestApiClient.ts b/apps/meteor/app/utils/client/lib/RestApiClient.ts index 0621fe9850da..38b2dc44b5ee 100644 --- a/apps/meteor/app/utils/client/lib/RestApiClient.ts +++ b/apps/meteor/app/utils/client/lib/RestApiClient.ts @@ -28,11 +28,16 @@ export const APIClient = new RestApiClient({ baseUrl: baseURI.replace(/\/$/, ''), }); -APIClient.use(function (request, next) { +APIClient.use(async function (request, next) { try { - return next(...request); - } catch (e) { - return new Promise((resolve, reject) => { + return await next(...request); + } catch (error) { + if (!(error instanceof Response) || error.status !== 400) { + throw error; + } + + return new Promise(async (resolve, reject) => { + const e = await error.json(); process2faReturn({ error: e, result: null, diff --git a/packages/api-client/src/index.ts b/packages/api-client/src/index.ts index 90905a7812b5..c75b39da3b38 100644 --- a/packages/api-client/src/index.ts +++ b/packages/api-client/src/index.ts @@ -161,6 +161,11 @@ export class RestClient implements RestClientInterface { ...options, headers: { ...this.getCredentialsAsHeaders(), ...this.headers, ...headers }, method, + }).then(function (response) { + if (!response.ok) { + return Promise.reject(response); + } + return response; }); } From 419f07ab48b0f9539019eba510a7f0276ba4990c Mon Sep 17 00:00:00 2001 From: Luciano Marcos Pierdona Junior <64279791+LucianoPierdona@users.noreply.github.com> Date: Thu, 16 Jun 2022 11:55:31 -0300 Subject: [PATCH 11/11] [FIX] Update import from `csv-parse` (#25872) ## Proposed changes (including videos or screenshots) This PR updates the importing of `csv-parse` because the used method wasn't working anymore, we were receiving the following error: `error: "this.csvParser is not a function"` ## Issue(s) ## Steps to test or reproduce ## Further comments --- apps/meteor/app/importer-csv/server/importer.js | 4 +++- apps/meteor/app/importer-slack-users/server/importer.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/importer-csv/server/importer.js b/apps/meteor/app/importer-csv/server/importer.js index d4d3d014dbfa..1d2ce78e38dd 100644 --- a/apps/meteor/app/importer-csv/server/importer.js +++ b/apps/meteor/app/importer-csv/server/importer.js @@ -7,7 +7,9 @@ export class CsvImporter extends Base { constructor(info, importRecord) { super(info, importRecord); - this.csvParser = require('csv-parse/lib/sync'); + const { parse } = require('csv-parse/lib/sync'); + + this.csvParser = parse; } prepareUsingLocalFile(fullFilePath) { diff --git a/apps/meteor/app/importer-slack-users/server/importer.js b/apps/meteor/app/importer-slack-users/server/importer.js index 5fcf3924499b..9646ac79e7b3 100644 --- a/apps/meteor/app/importer-slack-users/server/importer.js +++ b/apps/meteor/app/importer-slack-users/server/importer.js @@ -11,7 +11,9 @@ export class SlackUsersImporter extends Base { constructor(info, importRecord) { super(info, importRecord); - this.csvParser = require('csv-parse/lib/sync'); + const { parse } = require('csv-parse/lib/sync'); + + this.csvParser = parse; this.userMap = new Map(); this.admins = []; // Array of ids of the users which are admins }