diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index a958538b13fc..7e9d5b65ee50 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -370,7 +370,7 @@ jobs: done cd ./apps/meteor - npm run test:playwright + npm run test:e2e - name: Store playwright test trace uses: actions/upload-artifact@v2 @@ -683,7 +683,7 @@ jobs: docker logs presence --tail=50 cd ./apps/meteor - IS_EE=true npm run test:playwright + IS_EE=true npm run test:e2e - name: Store playwright test trace uses: actions/upload-artifact@v2 diff --git a/apps/meteor/.eslintignore b/apps/meteor/.eslintignore index 9b8e18053149..158360ed08eb 100644 --- a/apps/meteor/.eslintignore +++ b/apps/meteor/.eslintignore @@ -3,7 +3,6 @@ data/ tests/e2e/test-failures/ packages/autoupdate/ packages/meteor-streams/ -packages/meteor-timesync/ app/emoji-emojione/generateEmojiIndex.js packages/rocketchat-livechat/assets/rocketchat-livechat.min.js packages/rocketchat-livechat/assets/rocket-livechat.js diff --git a/apps/meteor/.meteor/packages b/apps/meteor/.meteor/packages index 1ddba77f0a0e..292ef0526ba7 100644 --- a/apps/meteor/.meteor/packages +++ b/apps/meteor/.meteor/packages @@ -56,7 +56,6 @@ jalik:ufs-local@1.0.4 jparker:gravatar kadira:flow-router -mizzao:timesync mrt:reactive-store mystor:device-detection rocketchat:restivus diff --git a/apps/meteor/.meteor/versions b/apps/meteor/.meteor/versions index 77c379cc90c9..2cb41ac914aa 100644 --- a/apps/meteor/.meteor/versions +++ b/apps/meteor/.meteor/versions @@ -76,7 +76,6 @@ meteorhacks:inject-initial@1.0.5 minifier-css@1.6.0 minifier-js@2.7.4 minimongo@1.8.0 -mizzao:timesync@0.3.4 mobile-experience@1.1.0 mobile-status-bar@1.1.0 modern-browsers@0.1.8 diff --git a/apps/meteor/.mocharc.api.js b/apps/meteor/.mocharc.api.js index 6fb62970b3f4..f6c09a541e00 100644 --- a/apps/meteor/.mocharc.api.js +++ b/apps/meteor/.mocharc.api.js @@ -11,8 +11,8 @@ module.exports = { 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/api/**/*.js', + 'tests/end-to-end/api/**/*.ts', 'tests/end-to-end/apps/*.js', 'tests/end-to-end/apps/*.ts', ], diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.room.js b/apps/meteor/app/e2e/client/rocketchat.e2e.room.js index 223644f56796..fea71e381182 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.room.js +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.room.js @@ -3,7 +3,6 @@ import { Base64 } from 'meteor/base64'; import { EJSON } from 'meteor/ejson'; import { Random } from 'meteor/random'; import { Session } from 'meteor/session'; -import { TimeSync } from 'meteor/mizzao:timesync'; import { Emitter } from '@rocket.chat/emitter'; import { e2e } from './rocketchat.e2e'; @@ -395,12 +394,7 @@ export class E2ERoom extends Emitter { // Helper function for encryption of messages encrypt(message) { - let ts; - if (isNaN(TimeSync.serverOffset())) { - ts = new Date(); - } else { - ts = new Date(Date.now() + TimeSync.serverOffset()); - } + const ts = new Date(); const data = new TextEncoder('UTF-8').encode( EJSON.stringify({ diff --git a/apps/meteor/app/lib/client/methods/sendMessage.js b/apps/meteor/app/lib/client/methods/sendMessage.js index 5019c6ec34a5..dad647d5be03 100644 --- a/apps/meteor/app/lib/client/methods/sendMessage.js +++ b/apps/meteor/app/lib/client/methods/sendMessage.js @@ -1,5 +1,4 @@ import { Meteor } from 'meteor/meteor'; -import { TimeSync } from 'meteor/mizzao:timesync'; import s from 'underscore.string'; import { ChatMessage, Rooms } from '../../../models/client'; @@ -19,7 +18,7 @@ Meteor.methods({ return dispatchToastMessage({ type: 'error', message: t('Message_Already_Sent') }); } const user = Meteor.user(); - message.ts = isNaN(TimeSync.serverOffset()) ? new Date() : new Date(Date.now() + TimeSync.serverOffset()); + message.ts = new Date(); message.u = { _id: Meteor.userId(), username: user.username, diff --git a/apps/meteor/app/livechat/server/api/lib/users.js b/apps/meteor/app/livechat/server/api/lib/users.js index 0ff2eebca64f..98f77cc144b0 100644 --- a/apps/meteor/app/livechat/server/api/lib/users.js +++ b/apps/meteor/app/livechat/server/api/lib/users.js @@ -1,7 +1,7 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Users } from '@rocket.chat/models'; -import { hasAllPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; +import { hasAllPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../../authorization/server/functions/hasPermission'; /** * @param {IRole['_id']} role the role id @@ -41,7 +41,7 @@ async function findUsers({ role, text, pagination: { offset, count, sort } }) { }; } export async function findAgents({ userId, text, pagination: { offset, count, sort } }) { - if (!(await hasAllPermissionAsync(userId, ['view-l-room', 'transfer-livechat-guest']))) { + if (!(await hasAtLeastOnePermissionAsync(userId, ['manage-livechat-agents', 'transfer-livechat-guest', 'edit-omnichannel-contact']))) { throw new Error('error-not-authorized'); } diff --git a/apps/meteor/app/livechat/server/api/rest.js b/apps/meteor/app/livechat/server/api/rest.js index 7273c565aac9..bfc6fe85465b 100644 --- a/apps/meteor/app/livechat/server/api/rest.js +++ b/apps/meteor/app/livechat/server/api/rest.js @@ -9,4 +9,4 @@ import './v1/customField.js'; import './v1/room.js'; import './v1/videoCall.js'; import './v1/transfer.js'; -import './v1/contact.js'; +import './v1/contact'; diff --git a/apps/meteor/app/livechat/server/api/v1/contact.js b/apps/meteor/app/livechat/server/api/v1/contact.js deleted file mode 100644 index af43442bdcf7..000000000000 --- a/apps/meteor/app/livechat/server/api/v1/contact.js +++ /dev/null @@ -1,77 +0,0 @@ -import { Match, check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; -import { LivechatVisitors } from '@rocket.chat/models'; - -import { API } from '../../../../api/server'; -import { Contacts } from '../../lib/Contacts'; - -API.v1.addRoute( - 'omnichannel/contact', - { authRequired: true }, - { - async post() { - try { - check(this.bodyParams, { - _id: Match.Maybe(String), - token: String, - name: String, - email: Match.Maybe(String), - phone: Match.Maybe(String), - customFields: Match.Maybe(Object), - contactManager: Match.Maybe({ - username: String, - }), - }); - - const contact = await Contacts.registerContact(this.bodyParams); - - return API.v1.success({ contact }); - } catch (e) { - return API.v1.failure(e); - } - }, - async get() { - check(this.queryParams, { - contactId: String, - }); - - const contact = await LivechatVisitors.findOneById(this.queryParams.contactId); - - return API.v1.success({ contact }); - }, - }, -); - -API.v1.addRoute( - 'omnichannel/contact.search', - { authRequired: true }, - { - async get() { - try { - check(this.queryParams, { - email: Match.Maybe(String), - phone: Match.Maybe(String), - }); - - const { email, phone } = this.queryParams; - - if (!email && !phone) { - throw new Meteor.Error('error-invalid-params'); - } - - const query = Object.assign( - {}, - { - ...(email && { visitorEmails: { address: email } }), - ...(phone && { phone: { phoneNumber: phone } }), - }, - ); - - const contact = await LivechatVisitors.findOne(query); - return API.v1.success({ contact }); - } catch (e) { - return API.v1.failure(e); - } - }, - }, -); diff --git a/apps/meteor/app/livechat/server/api/v1/contact.ts b/apps/meteor/app/livechat/server/api/v1/contact.ts new file mode 100644 index 000000000000..baf9eb6410e7 --- /dev/null +++ b/apps/meteor/app/livechat/server/api/v1/contact.ts @@ -0,0 +1,70 @@ +import { Match, check } from 'meteor/check'; +import { Meteor } from 'meteor/meteor'; +import { LivechatVisitors } from '@rocket.chat/models'; + +import { API } from '../../../../api/server'; +import { Contacts } from '../../lib/Contacts'; + +API.v1.addRoute( + 'omnichannel/contact', + { authRequired: true }, + { + async post() { + check(this.bodyParams, { + _id: Match.Maybe(String), + token: String, + name: String, + email: Match.Maybe(String), + phone: Match.Maybe(String), + username: Match.Maybe(String), + customFields: Match.Maybe(Object), + contactManager: Match.Maybe({ + username: String, + }), + }); + + const contact = await Contacts.registerContact(this.bodyParams); + + return API.v1.success({ contact }); + }, + async get() { + check(this.queryParams, { + contactId: String, + }); + + const contact = await LivechatVisitors.findOneById(this.queryParams.contactId); + + return API.v1.success({ contact }); + }, + }, +); + +API.v1.addRoute( + 'omnichannel/contact.search', + { authRequired: true }, + { + async get() { + check(this.queryParams, { + email: Match.Maybe(String), + phone: Match.Maybe(String), + }); + + const { email, phone } = this.queryParams; + + if (!email && !phone) { + throw new Meteor.Error('error-invalid-params'); + } + + const query = Object.assign( + {}, + { + ...(email && { visitorEmails: { address: email } }), + ...(phone && { phone: { phoneNumber: phone } }), + }, + ); + + const contact = await LivechatVisitors.findOne(query); + return API.v1.success({ contact }); + }, + }, +); diff --git a/apps/meteor/app/livechat/server/lib/Contacts.js b/apps/meteor/app/livechat/server/lib/Contacts.ts similarity index 55% rename from apps/meteor/app/livechat/server/lib/Contacts.js rename to apps/meteor/app/livechat/server/lib/Contacts.ts index a43d4867aa79..45fbf828fdb2 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.js +++ b/apps/meteor/app/livechat/server/lib/Contacts.ts @@ -1,12 +1,35 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; -import { LivechatVisitors, LivechatRooms, Users } from '@rocket.chat/models'; +import { MatchKeysAndValues, OnlyFieldsOfType } from 'mongodb'; +import { LivechatVisitors, Users, LivechatRooms } from '@rocket.chat/models'; +import { ILivechatCustomField, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; import { LivechatCustomField, Rooms, LivechatInquiry, Subscriptions } from '../../../models/server'; +type RegisterContactProps = { + _id?: string; + token: string; + name: string; + username?: string; + email?: string; + phone?: string; + customFields?: Record; + contactManager?: { + username: string; + }; +}; + export const Contacts = { - async registerContact({ token, name, email, phone, username, customFields = {}, contactManager = {} } = {}) { + async registerContact({ + token, + name, + email = '', + phone, + username, + customFields = {}, + contactManager, + }: RegisterContactProps): Promise { check(token, String); const visitorEmail = s.trim(email).toLowerCase(); @@ -23,11 +46,6 @@ export const Contacts = { } let contactId; - const updateUser = { - $set: { - token, - }, - }; const user = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } }); @@ -46,31 +64,39 @@ export const Contacts = { const userData = { username, ts: new Date(), + token, }; - contactId = await LivechatVisitors.insertOne(userData); + contactId = (await LivechatVisitors.insertOne(userData)).insertedId; } } - updateUser.$set.name = name; - updateUser.$set.phone = (phone && [{ phoneNumber: phone }]) || null; - updateUser.$set.visitorEmails = (visitorEmail && [{ address: visitorEmail }]) || null; - - const allowedCF = LivechatCustomField.find({ scope: 'visitor' }, { fields: { _id: 1 } }).map(({ _id }) => _id); + const allowedCF: ILivechatCustomField['_id'][] = LivechatCustomField.find({ scope: 'visitor' }, { fields: { _id: 1 } }).map( + ({ _id }: ILivechatCustomField) => _id, + ); const livechatData = Object.keys(customFields) .filter((key) => allowedCF.includes(key) && customFields[key] !== '' && customFields[key] !== undefined) - .reduce((obj, key) => { + .reduce((obj: Record, key) => { obj[key] = customFields[key]; return obj; }, {}); - updateUser.$set.livechatData = livechatData; - updateUser.$set.contactManager = (contactManager?.username && { username: contactManager.username }) || null; + const updateUser: { $set: MatchKeysAndValues; $unset?: OnlyFieldsOfType } = { + $set: { + token, + name, + livechatData, + ...(phone && { phone: [{ phoneNumber: phone }] }), + ...(visitorEmail && { visitorEmails: [{ address: visitorEmail }] }), + ...(contactManager?.username && { contactManager: { username: contactManager.username } }), + }, + ...(!contactManager?.username && { $unset: { contactManager: 1 } }), + }; - await LivechatVisitors.updateById(contactId, updateUser); + await LivechatVisitors.updateOne({ _id: contactId }, updateUser); - const rooms = await LivechatRooms.findByVisitorId(contactId).toArray(); + const rooms: IOmnichannelRoom[] = await LivechatRooms.findByVisitorId(contactId, {}).toArray(); rooms?.length && rooms.forEach((room) => { diff --git a/apps/meteor/app/otr/client/rocketchat.otr.room.js b/apps/meteor/app/otr/client/rocketchat.otr.room.js index cf9dcd37308d..9354501a7fcc 100644 --- a/apps/meteor/app/otr/client/rocketchat.otr.room.js +++ b/apps/meteor/app/otr/client/rocketchat.otr.room.js @@ -4,7 +4,6 @@ import { Random } from 'meteor/random'; import { EJSON } from 'meteor/ejson'; import { Tracker } from 'meteor/tracker'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { TimeSync } from 'meteor/mizzao:timesync'; import _ from 'underscore'; import { OTR } from './rocketchat.otr'; @@ -219,12 +218,7 @@ OTR.Room = class { } encrypt(message) { - let ts; - if (isNaN(TimeSync.serverOffset())) { - ts = new Date(); - } else { - ts = new Date(Date.now() + TimeSync.serverOffset()); - } + const ts = new Date(); const data = new TextEncoder('UTF-8').encode( EJSON.stringify({ diff --git a/apps/meteor/client/components/AutoCompleteAgent.js b/apps/meteor/client/components/AutoCompleteAgent.js index 92659ff93c12..fcbfe7b685b6 100644 --- a/apps/meteor/client/components/AutoCompleteAgent.js +++ b/apps/meteor/client/components/AutoCompleteAgent.js @@ -7,26 +7,29 @@ import { AsyncStatePhase } from '../lib/asyncState'; import { useAgentsList } from './Omnichannel/hooks/useAgentsList'; const AutoCompleteAgent = (props) => { - const { value, onChange = () => {}, haveAll = false } = props; + const { value, onChange = () => {}, haveAll = false, haveNoAgentsSelectedOption = false } = props; const [agentsFilter, setAgentsFilter] = useState(''); const debouncedAgentsFilter = useDebouncedValue(agentsFilter, 500); const { itemsList: AgentsList, loadMoreItems: loadMoreAgents } = useAgentsList( - useMemo(() => ({ text: debouncedAgentsFilter, haveAll }), [debouncedAgentsFilter, haveAll]), + useMemo( + () => ({ text: debouncedAgentsFilter, haveAll, haveNoAgentsSelectedOption }), + [debouncedAgentsFilter, haveAll, haveNoAgentsSelectedOption], + ), ); const { phase: agentsPhase, items: agentsItems, itemCount: agentsTotal } = useRecordList(AgentsList); const sortedByName = agentsItems.sort((a, b) => { - if (a.value === 'all') { + if (['all', 'no-agent-selected'].includes(a.value)) { return -1; } - if (a.usename > b.usename) { + if (a.username > b.username) { return 1; } - if (a.usename < b.usename) { + if (a.username < b.username) { return -1; } diff --git a/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts index 732cfa615740..ecdb55aa1da4 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts @@ -9,6 +9,7 @@ import { RecordList } from '../../../lib/lists/RecordList'; type AgentsListOptions = { text: string; haveAll: boolean; + haveNoAgentsSelectedOption: boolean; }; export const useAgentsList = ( @@ -52,12 +53,19 @@ export const useAgentsList = ( _updatedAt: new Date(), }); + options.haveNoAgentsSelectedOption && + items.unshift({ + label: t('Empty_no_agent_selected'), + value: 'no-agent-selected', + _updatedAt: new Date(), + }); + return { items, itemCount: total + 1, }; }, - [getAgents, options.haveAll, options.text, t], + [getAgents, options.haveAll, options.haveNoAgentsSelectedOption, options.text, t], ); const { loadMoreItems, initialItemCount } = useScrollableRecordList(itemsList, fetchData, 25); diff --git a/apps/meteor/client/methods/updateMessage.ts b/apps/meteor/client/methods/updateMessage.ts index 8aa4ddaed1b2..9efe68a452e8 100644 --- a/apps/meteor/client/methods/updateMessage.ts +++ b/apps/meteor/client/methods/updateMessage.ts @@ -1,5 +1,4 @@ import { Meteor } from 'meteor/meteor'; -import { TimeSync } from 'meteor/mizzao:timesync'; import { Tracker } from 'meteor/tracker'; import moment from 'moment'; import _ from 'underscore'; @@ -20,6 +19,9 @@ Meteor.methods({ const originalMessage = ChatMessage.findOne(message._id); + if (!originalMessage) { + return; + } const hasPermission = hasAtLeastOnePermission('edit-message', message.rid); const editAllowed = settings.get('Message_AllowEditing'); let editOwn = false; @@ -59,11 +61,7 @@ Meteor.methods({ } Tracker.nonreactive(() => { - if (isNaN(TimeSync.serverOffset())) { - message.editedAt = new Date(); - } else { - message.editedAt = new Date(Date.now() + TimeSync.serverOffset()); - } + message.editedAt = new Date(Date.now()); message.editedBy = { _id: uid, diff --git a/apps/meteor/client/startup/startup.ts b/apps/meteor/client/startup/startup.ts index 40776bf14ca8..fa9f728d1243 100644 --- a/apps/meteor/client/startup/startup.ts +++ b/apps/meteor/client/startup/startup.ts @@ -2,7 +2,6 @@ import { UserStatus } from '@rocket.chat/core-typings'; import { Accounts } from 'meteor/accounts-base'; import { UserPresence } from 'meteor/konecty:user-presence'; import { Meteor } from 'meteor/meteor'; -import { TimeSync } from 'meteor/mizzao:timesync'; import { Session } from 'meteor/session'; import { Tracker } from 'meteor/tracker'; import moment from 'moment'; @@ -21,8 +20,6 @@ Meteor.startup(() => { Accounts.onLogout(() => Session.set('openedRoom', null)); - TimeSync.loggingEnabled = false; - Session.setDefault('AvatarRandom', 0); window.lastMessageWindow = {}; diff --git a/apps/meteor/client/views/admin/permissions/hooks/usePermissionsAndRoles.ts b/apps/meteor/client/views/admin/permissions/hooks/usePermissionsAndRoles.ts index e021674962f8..02454510f2b0 100644 --- a/apps/meteor/client/views/admin/permissions/hooks/usePermissionsAndRoles.ts +++ b/apps/meteor/client/views/admin/permissions/hooks/usePermissionsAndRoles.ts @@ -1,5 +1,6 @@ import type { IRole, IPermission } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { escapeRegExp } from '@rocket.chat/string-helpers'; import { useCallback } from 'react'; import { ChatPermissions } from '../../../../../app/authorization/client/lib/ChatPermissions'; @@ -13,27 +14,32 @@ export const usePermissionsAndRoles = ( limit = 25, skip = 0, ): { permissions: IPermission[]; total: number; roleList: IRole[]; reload: () => void } => { - const getPermissions = useCallback(() => { - const filterRegExp = new RegExp(filter, 'i'); + const getFilter = useCallback(() => { + const filterRegExp = new RegExp(escapeRegExp(filter), 'i'); - return ChatPermissions.find( - { - level: type === 'permissions' ? { $ne: CONSTANTS.SETTINGS_LEVEL } : CONSTANTS.SETTINGS_LEVEL, - _id: filterRegExp, - }, - { + return { + level: type === 'permissions' ? { $ne: CONSTANTS.SETTINGS_LEVEL } : CONSTANTS.SETTINGS_LEVEL, + _id: filterRegExp, + }; + }, [type, filter]); + + const getPermissions = useCallback( + () => + ChatPermissions.find(getFilter(), { sort: { _id: 1, }, skip, limit, - }, - ); - }, [filter, limit, skip, type]); + }), + [limit, skip, getFilter], + ); + const getTotalPermissions = useCallback(() => ChatPermissions.find(getFilter()).count(), [getFilter]); const permissions = useReactiveValue(getPermissions); + const permissionsTotal = useReactiveValue(getTotalPermissions); const getRoles = useMutableCallback(() => Roles.find().fetch()); const roles = useReactiveValue(getRoles); - return { permissions: permissions.fetch(), total: permissions.count(false), roleList: roles, reload: getRoles }; + return { permissions: permissions.fetch(), total: permissionsTotal, roleList: roles, reload: getRoles }; }; diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js index 214f47dcebb3..975030376194 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js +++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js @@ -1,7 +1,7 @@ import { Field, TextInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import { hasAtLeastOnePermission } from '../../../../../../app/authorization/client'; import { validateEmail } from '../../../../../../lib/emailValidator'; @@ -47,7 +47,11 @@ function ContactNewEdit({ id, data, close }) { const canViewCustomFields = () => hasAtLeastOnePermission(['view-livechat-room-customfields', 'edit-livechat-room-customfields']); - const { values, handlers, hasUnsavedChanges: hasUnsavedChangesContact } = useForm(getInitialValues(data)); + const initialValue = getInitialValues(data); + + const { username: initialUsername } = initialValue; + + const { values, handlers, hasUnsavedChanges: hasUnsavedChangesContact } = useForm(initialValue); const eeForms = useFormsSubscription(); @@ -73,6 +77,7 @@ function ContactNewEdit({ id, data, close }) { const [emailError, setEmailError] = useState(); const [phoneError, setPhoneError] = useState(); const [customFieldsError, setCustomFieldsError] = useState([]); + const [userId, setUserId] = useState('no-agent-selected'); const { value: allCustomFields, phase: state } = useEndpointData('/v1/livechat/custom-fields'); @@ -99,6 +104,7 @@ function ContactNewEdit({ id, data, close }) { const saveContact = useEndpoint('POST', '/v1/omnichannel/contact'); const emailAlreadyExistsAction = useEndpoint('GET', '/v1/omnichannel/contact.search'); const phoneAlreadyExistsAction = useEndpoint('GET', '/v1/omnichannel/contact.search'); + const getUserData = useEndpoint('GET', '/v1/users.info'); const checkEmailExists = useMutableCallback(async () => { if (!validateEmail(email)) { @@ -134,6 +140,28 @@ function ContactNewEdit({ id, data, close }) { !phone && setPhoneError(null); }, [phone]); + useEffect(() => { + if (!initialUsername) { + return; + } + + getUserData({ username: initialUsername }).then(({ user }) => { + setUserId(user._id); + }); + }, [getUserData, initialUsername]); + + const handleContactManagerChange = useMutableCallback(async (userId) => { + setUserId(userId); + if (userId === 'no-agent-selected') { + handleUsername(''); + return; + } + + getUserData({ userId }).then(({ user }) => { + handleUsername(user.username); + }); + }); + const handleSave = useMutableCallback(async (e) => { e.preventDefault(); let error = false; @@ -152,18 +180,13 @@ function ContactNewEdit({ id, data, close }) { const payload = { name, + phone, + email, + customFields: livechatData || {}, + token: token || createToken(), + ...(username && { contactManager: { username } }), + ...(id && { _id: id }), }; - payload.phone = phone; - payload.email = email; - payload.customFields = livechatData || {}; - payload.contactManager = username ? { username } : {}; - - if (id) { - payload._id = id; - payload.token = token; - } else { - payload.token = createToken(); - } try { await saveContact(payload); @@ -213,7 +236,7 @@ function ContactNewEdit({ id, data, close }) { setCustomFieldsError={setCustomFieldsError} /> )} - {ContactManager && } + {ContactManager && } diff --git a/apps/meteor/definition/externals/meteor/mizzao-timesync.d.ts b/apps/meteor/definition/externals/meteor/mizzao-timesync.d.ts deleted file mode 100644 index 7ebaa77b2bea..000000000000 --- a/apps/meteor/definition/externals/meteor/mizzao-timesync.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module 'meteor/mizzao:timesync' { - namespace TimeSync { - let loggingEnabled: boolean; - - function serverOffset(): number; - } -} diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/ContactManager.js b/apps/meteor/ee/client/omnichannel/additionalForms/ContactManager.js index 04a012aae08b..8c0d0342b03a 100644 --- a/apps/meteor/ee/client/omnichannel/additionalForms/ContactManager.js +++ b/apps/meteor/ee/client/omnichannel/additionalForms/ContactManager.js @@ -1,39 +1,17 @@ import { Field } from '@rocket.chat/fuselage'; -import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useEffect, useState, useCallback } from 'react'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; import AutoCompleteAgent from '../../../../client/components/AutoCompleteAgent'; -export const ContactManager = ({ value: username, handler }) => { +export const ContactManager = ({ value: userId, handler }) => { const t = useTranslation(); - const [userId, setUserId] = useState(); - - const getUserData = useEndpoint('GET', '/v1/users.info'); - - const fetchUserId = async () => { - const { user } = await getUserData({ username }); - user._id && setUserId(user._id); - }; - - const handleAgent = useCallback( - async (e) => { - setUserId(e); - const { user } = await getUserData({ userId: e }); - handler(user.username); - }, - [handler, setUserId, getUserData], - ); - - useEffect(() => { - fetchUserId(); - }); - return ( {t('Contact_Manager')} - + ); diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 973a89236a2a..7964a19b8066 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -32,7 +32,7 @@ "typecheck": "cross-env NODE_OPTIONS=\"--max-old-space-size=6144\" tsc -p tsconfig.typecheck.json", "deploy": "npm run build && pm2 startOrRestart pm2.json", "coverage": "nyc -r html mocha --config ./.mocharc.js", - "test:playwright": "playwright test", + "test:e2e": "playwright test", "testapi": "mocha --config ./.mocharc.api.js", "testunit": "npm run .testunit:definition && npm run .testunit:client && npm run .testunit:server", ".testunit:server": "mocha --config ./.mocharc.js", diff --git a/apps/meteor/packages/meteor-timesync/.gitignore b/apps/meteor/packages/meteor-timesync/.gitignore deleted file mode 100644 index 3ccf4f8cd66e..000000000000 --- a/apps/meteor/packages/meteor-timesync/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.build* -.versions diff --git a/apps/meteor/packages/meteor-timesync/.travis.yml b/apps/meteor/packages/meteor-timesync/.travis.yml deleted file mode 100644 index d826297b185e..000000000000 --- a/apps/meteor/packages/meteor-timesync/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - "0.10" -before_install: - - "curl -L http://git.io/ejPSng | /bin/sh" diff --git a/apps/meteor/packages/meteor-timesync/History.md b/apps/meteor/packages/meteor-timesync/History.md deleted file mode 100644 index 77529b8de360..000000000000 --- a/apps/meteor/packages/meteor-timesync/History.md +++ /dev/null @@ -1,72 +0,0 @@ -## vNEXT - -## v0.3.4 - -- Explicitly pull in client-side `check` for Meteor 1.2 apps. - -## v0.3.3 - -- Be more robust with sync url when outside of Cordova. (#30) - -## v0.3.2 - -- Fix issue when used in Cordova. (#22, #26, #27) - -## v0.3.1 - -- Fix an issue where `TimeSync.serverTime` returned an erroneous value when passed a `Date` (instead of an epoch). (#23) - -## v0.3.0 - -- `TimeSync.serverTime` now supports an optional second `updateInterval` argument, causing the reactive value to update less frequently. (#10) -- `TimeSync.loggingEnabled` can be now set to false to suppress client log output. (#21) -- Explicitly set MIME type on timesync endpoint. (#17, #18) - -## v0.2.2 - -- **Updated for Meteor 0.9.** -- Further adjust clock watching tolerance to be less sensitive to CPU. - -## v0.2.1 - -- Re-sync automatically after a reconnection. -- Adjust clock watching tolerance so as to be less sensitive to heavy client CPU usage. - -## v0.2.0 - -- Clock change watching is now on by default (it's very lightweight and only involves grabbing and checking a `Date`). -- Invalidate offset value and dependent time computations when we detect a clock change. -- Added a `Date.now` shim for earlier versions of IE. -- Reorganized code for testing and added some basic tests. - -## v0.1.6 - -- Added the optional `TimeSync.watchClockChanges` which can resync if a client's clock is detected to have significantly changed. -- Added retry attempts to syncing, making it more robust over a hot code reload among other situations. - -## v0.1.5 - -- Use `WebApp.rawConnectHandlers` as a less janky way of getting our date request handled first. -- Fixed an issue where a cached reload could result in a wacky time offset due to the server time being cached. - -## v0.1.4 - -- Switch to JS at the request of @raix and @arunoda ;-) -- Use a middleware handler, spliced into the top of the connect stack, instead of a Meteor method to avoid arbitrary method blocking delay. This improves accuracy significantly. -- Compute a RTT value in `TimeSync.roundTripTime` as well as a time offset. - -## v0.1.3 - -- Ensure that the computed offset is always an integer number of milliseconds. - -## v0.1.2 - -- Added the `TimeSync.resync` function that triggers a resync with the server. - -## v0.1.1 - -- Added the reactive function `TimeSync.isSynced` to determine if an initial sync has taken place. - -## v0.1.0 - -- First release. diff --git a/apps/meteor/packages/meteor-timesync/LICENSE b/apps/meteor/packages/meteor-timesync/LICENSE deleted file mode 100644 index d7405464dc3f..000000000000 --- a/apps/meteor/packages/meteor-timesync/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Andrew Mao - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/apps/meteor/packages/meteor-timesync/README.md b/apps/meteor/packages/meteor-timesync/README.md deleted file mode 100644 index e194c6bc997d..000000000000 --- a/apps/meteor/packages/meteor-timesync/README.md +++ /dev/null @@ -1,38 +0,0 @@ -meteor-timesync [![Build Status](https://travis-ci.org/mizzao/meteor-timesync.svg)](https://travis-ci.org/mizzao/meteor-timesync) -=============== - -NTP-style time synchronization between server and client, and facilities to -use server time reactively in Meteor applications. - -## What's this do? - -Meteor clients don't necessarily have accurate timestamps relative to your server. This package computes and maintains an offset between server and client, allowing server timestamps to be used on the client (especially for displaying time differences). It also provides facilities to use time reactively in your application. - -There is a demo as part of the user-status app at http://user-status.meteor.com. - -## Installation - -``` -meteor add mizzao:timesync -``` - -## Usage - -- `TimeSync.serverTime(clientTime, updateInterval)`: returns the server time for a given client time, as a UTC/Unix timestamp. A reactive variable which changes with the computed offset, and updates continually. Pass in `clientTime` optionally to specify a particular time on the client, instead of reactively depending on the current time. Pass in `updateInterval` to change the rate (in milliseconds) at which the reactive variable updates; the default value is 1000 (1 second). -- `TimeSync.serverOffset()`: returns the current time difference between the server and the client. Reactively updates as the offset is recomputed. -- `TimeSync.roundTripTime()`: The round trip ping to the server. Also reactive. -- `TimeSync.isSynced()`: Reactive variable that determines if an initial sync has taken place. -- `TimeSync.resync()`: Re-triggers a sync with the server. Can be useful because the initial sync often takes place during a lot of traffic with the server and could be less accurate. -- `TimeSync.loggingEnabled`: defaults to `true`, set this to `false` to suppress diagnostic syncing messages on the client. - -To use the above functions in a non-reactive context, use [`Deps.nonreactive`](http://docs.meteor.com/#deps_nonreactive). This is useful if you are displaying a lot of timestamps or differences on a page and you don't want them to be constantly recomputed on the client. However, displaying time reactively should be pretty efficient with Meteor 0.8.0+ (Blaze). - -Note that `TimeSync.serverTime` returns a timestamp, not a `Date`, but you can easily construct a date with `new Date(TimeSync.serverTime(...))`. - -You can also use something like `TimeSync.serverTime(null, 5000)` to get a reactive time value that only updates at 5 second intervals. All reactive time variables with the same value of `updateInterval` are guaranteed to be invalidated at the same time. - -## Notes - -- This library is a crude approximation of NTP, at the moment. It's empirically shown to be accurate to under 100 ms on the meteor.com servers. -- We could definitely do something smarter and more accurate, with multiple measurements and exponentially weighted updating. -- Check out the moment library [packaged for meteor](https://github.com/acreeger/meteor-moment) for formatting and displaying the differences computed by this package. diff --git a/apps/meteor/packages/meteor-timesync/client/index.js b/apps/meteor/packages/meteor-timesync/client/index.js deleted file mode 100644 index b9a53fb0ddf6..000000000000 --- a/apps/meteor/packages/meteor-timesync/client/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import { TimeSync, SyncInternals } from './timesync-client'; - -export { - TimeSync, - SyncInternals, -}; diff --git a/apps/meteor/packages/meteor-timesync/client/timesync-client.js b/apps/meteor/packages/meteor-timesync/client/timesync-client.js deleted file mode 100644 index c571f7e7cbca..000000000000 --- a/apps/meteor/packages/meteor-timesync/client/timesync-client.js +++ /dev/null @@ -1,171 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; -import { HTTP } from 'meteor/http'; - -//IE8 doesn't have Date.now() -Date.now = Date.now || function() { return +new Date; }; - -export const TimeSync = { - loggingEnabled: true -}; - -function log(/* arguments */) { - if (TimeSync.loggingEnabled) { - Meteor._debug.apply(this, arguments); - } -} - -var defaultInterval = 1000; - -// Internal values, exported for testing -export const SyncInternals = { - offset: undefined, - roundTripTime: undefined, - offsetDep: new Tracker.Dependency(), - timeTick: {}, - - timeCheck: function (lastTime, currentTime, interval, tolerance) { - if (Math.abs(currentTime - lastTime - interval) < tolerance) { - // Everything is A-OK - return true; - } - // We're no longer in sync. - return false; - } -}; - -SyncInternals.timeTick[defaultInterval] = new Tracker.Dependency(); - -var maxAttempts = 5; -var attempts = 0; - -/* - This is an approximation of - http://en.wikipedia.org/wiki/Network_Time_Protocol - - If this turns out to be more accurate under the connect handlers, - we should try taking multiple measurements. - */ - -var syncUrl = "/_timesync"; -if (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX) { - syncUrl = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + syncUrl; -} - -var updateOffset = function() { - var t0 = Date.now(); - - HTTP.get(syncUrl, function(err, response) { - var t3 = Date.now(); // Grab this now - if (err) { - // We'll still use our last computed offset if is defined - log("Error syncing to server time: ", err); - if (++attempts <= maxAttempts) - Meteor.setTimeout(TimeSync.resync, 1000); - else - log("Max number of time sync attempts reached. Giving up."); - return; - } - - attempts = 0; // It worked - - var ts = parseInt(response.content); - SyncInternals.offset = Math.round(((ts - t0) + (ts - t3)) / 2); - SyncInternals.roundTripTime = t3 - t0; // - (ts - ts) which is 0 - SyncInternals.offsetDep.changed(); - }); -}; - -// Reactive variable for server time that updates every second. -TimeSync.serverTime = function(clientTime, interval) { - check(interval, Match.Optional(Match.Integer)); - // If we don't know the offset, we can't provide the server time. - if ( !TimeSync.isSynced() ) return undefined; - // If a client time is provided, we don't need to depend on the tick. - if ( !clientTime ) getTickDependency(interval || defaultInterval).depend(); - - // SyncInternals.offsetDep.depend(); implicit as we call isSynced() - // Convert Date argument to epoch as necessary - return (+clientTime || Date.now()) + SyncInternals.offset; -}; - -// Reactive variable for the difference between server and client time. -TimeSync.serverOffset = function() { - SyncInternals.offsetDep.depend(); - return SyncInternals.offset; -}; - -TimeSync.roundTripTime = function() { - SyncInternals.offsetDep.depend(); - return SyncInternals.roundTripTime; -}; - -TimeSync.isSynced = function() { - SyncInternals.offsetDep.depend(); - return SyncInternals.offset !== undefined; -}; - -var resyncIntervalId = null; - -TimeSync.resync = function() { - if (resyncIntervalId !== null) Meteor.clearInterval(resyncIntervalId); - updateOffset(); - resyncIntervalId = Meteor.setInterval(updateOffset, 600000); -}; - -// Run this as soon as we load, even before Meteor.startup() -// Run again whenever we reconnect after losing connection -var wasConnected = false; - -Tracker.autorun(function() { - var connected = Meteor.status().connected; - if ( connected && !wasConnected ) TimeSync.resync(); - wasConnected = connected; -}); - -// Resync if unexpected change by more than a few seconds. This needs to be -// somewhat lenient, or a CPU-intensive operation can trigger a re-sync even -// when the offset is still accurate. In any case, we're not going to be able to -// catch very small system-initiated NTP adjustments with this, anyway. -var tickCheckTolerance = 5000; - -var lastClientTime = Date.now(); - -// Set up a new interval for any amount of reactivity. -function getTickDependency(interval) { - - if ( !SyncInternals.timeTick[interval] ) { - var dep = new Tracker.Dependency(); - - Meteor.setInterval(function() { - dep.changed(); - }, interval); - - SyncInternals.timeTick[interval] = dep; - } - - return SyncInternals.timeTick[interval]; -} - -// Set up special interval for the default tick, which also watches for re-sync -Meteor.setInterval(function() { - var currentClientTime = Date.now(); - - if ( SyncInternals.timeCheck( - lastClientTime, currentClientTime, defaultInterval, tickCheckTolerance) ) { - // No problem here, just keep ticking along - SyncInternals.timeTick[defaultInterval].changed(); - } - else { - // resync on major client clock changes - // based on http://stackoverflow.com/a/3367542/1656818 - log("Clock discrepancy detected. Attempting re-sync."); - // Refuse to compute server time. - SyncInternals.offset = undefined; - SyncInternals.offsetDep.changed(); - TimeSync.resync(); - } - - lastClientTime = currentClientTime; -}, defaultInterval); - diff --git a/apps/meteor/packages/meteor-timesync/package.js b/apps/meteor/packages/meteor-timesync/package.js deleted file mode 100644 index 175327300c9c..000000000000 --- a/apps/meteor/packages/meteor-timesync/package.js +++ /dev/null @@ -1,38 +0,0 @@ -Package.describe({ - name: 'mizzao:timesync', - summary: 'NTP-style time synchronization between server and client', - version: '0.3.4', - git: 'https://github.com/mizzao/meteor-timesync.git' -}); - -Package.onUse(function (api) { - api.use([ - 'check', - 'tracker', - 'http', - ], 'client'); - - api.use([ - 'webapp', - ], 'server'); - - api.use([ - 'ecmascript', - ]); - api.mainModule('client/index.js', 'client'); - api.mainModule('server/index.js', 'server'); -}); - -Package.onTest(function (api) { - api.use([ - 'ecmascript', - 'tinytest', - 'test-helpers' - ]); - - api.use(['tracker'], 'client'); - - api.use('mizzao:timesync'); - - api.addFiles('tests/client.js', 'client'); -}); diff --git a/apps/meteor/packages/meteor-timesync/server/index.js b/apps/meteor/packages/meteor-timesync/server/index.js deleted file mode 100644 index ab8e57008da6..000000000000 --- a/apps/meteor/packages/meteor-timesync/server/index.js +++ /dev/null @@ -1 +0,0 @@ -import './timesync-server'; \ No newline at end of file diff --git a/apps/meteor/packages/meteor-timesync/server/timesync-server.js b/apps/meteor/packages/meteor-timesync/server/timesync-server.js deleted file mode 100644 index 94a8db1ac83f..000000000000 --- a/apps/meteor/packages/meteor-timesync/server/timesync-server.js +++ /dev/null @@ -1,23 +0,0 @@ -import { WebApp } from 'meteor/webapp'; -// Use rawConnectHandlers so we get a response as quickly as possible -// https://github.com/meteor/meteor/blob/devel/packages/webapp/webapp_server.js - -var syncUrl = "/_timesync"; -if (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX) { - syncUrl = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + syncUrl; -} - -WebApp.rawConnectHandlers.use(syncUrl, - function(req, res, next) { - // Never ever cache this, otherwise weird times are shown on reload - // http://stackoverflow.com/q/18811286/586086 - res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - res.setHeader("Pragma", "no-cache"); - res.setHeader("Expires", 0); - - // Avoid MIME type warnings in browsers - res.setHeader("Content-Type", "text/plain"); - - res.end(Date.now().toString()); - } -); diff --git a/apps/meteor/packages/meteor-timesync/tests/client.js b/apps/meteor/packages/meteor-timesync/tests/client.js deleted file mode 100644 index bbbe83c75df4..000000000000 --- a/apps/meteor/packages/meteor-timesync/tests/client.js +++ /dev/null @@ -1,121 +0,0 @@ -Tinytest.add("timesync - tick check - normal tick", function(test) { - var lastTime = 5000; - var currentTime = 6000; - var interval = 1000; - var tolerance = 1000; - - test.equal(SyncInternals.timeCheck(lastTime, currentTime, interval, tolerance), true); -}); - -Tinytest.add("timesync - tick check - slightly off", function(test) { - var lastTime = 5000; - var currentTime = 6500; - var interval = 1000; - var tolerance = 1000; - - test.equal(SyncInternals.timeCheck(lastTime, currentTime, interval, tolerance), true); - - currentTime = 5500; - - test.equal(SyncInternals.timeCheck(lastTime, currentTime, interval, tolerance), true); -}); - -Tinytest.add("timesync - tick check - big jump", function(test) { - var lastTime = 5000; - var currentTime = 0; - var interval = 1000; - var tolerance = 1000; - - test.equal(SyncInternals.timeCheck(lastTime, currentTime, interval, tolerance), false); - - currentTime = 10000; - - test.equal(SyncInternals.timeCheck(lastTime, currentTime, interval, tolerance), false); -}); - -/* - TODO: add tests for proper dependencies in reactive functions - */ - -Tinytest.addAsync("timesync - basic - initial sync", function(test, next) { - - function success() { - var syncedTime = TimeSync.serverTime(); - - // Make sure the time exists - test.isTrue(syncedTime); - - // Make sure it's close to the current time on the client. This should - // always be true in PhantomJS tests where client/server are the same - // machine, although it might fail in development environments, for example - // when the server and client are different VMs. - test.isTrue( Math.abs(syncedTime - Date.now()) < 1000 ); - - next(); - } - - function fail() { - test.fail(); - next(); - } - - simplePoll(TimeSync.isSynced, success, fail, 5000, 100); -}); - -Tinytest.addAsync("timesync - basic - serverTime format", function(test, next) { - - test.isTrue(_.isNumber( TimeSync.serverTime() )); - - test.isTrue(_.isNumber( TimeSync.serverTime(null) )); - - // Accept Date as client time - test.isTrue(_.isNumber( TimeSync.serverTime(new Date()) )); - - // Accept epoch as client time - test.isTrue(_.isNumber( TimeSync.serverTime(Date.now()) )); - - next(); -}); - -Tinytest.addAsync("timesync - basic - different sync intervals", function(test, next) { - - var aCount = 0, bCount = 0, cCount = 0; - - var a = Tracker.autorun(function () { - TimeSync.serverTime(null, 500); - aCount++; - }); - - var b = Tracker.autorun(function () { - TimeSync.serverTime(); - bCount++; - }); - - var c = Tracker.autorun(function () { - TimeSync.serverTime(null, 2000); - cCount++; - }); - - var testInterval = 5000; - - Meteor.setTimeout(function() { - - test.equal(aCount, 10); // 0, 500, 1000, 1500 ... - // not going to be 5 since the first tick won't generate this dep - test.equal(bCount, 6); - test.equal(cCount, 3); // 0, 2000, 4000 - - test.isTrue(SyncInternals.timeTick[500]); - test.isTrue(SyncInternals.timeTick[1000]); - test.isTrue(SyncInternals.timeTick[2000]); - - test.equal(Object.keys(SyncInternals.timeTick).length, 3); - - a.stop(); - b.stop(); - c.stop(); - - next() - }, testInterval); - -}); diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index fbea196af072..007bf722335f 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1690,6 +1690,7 @@ "Emoji_provided_by_JoyPixels": "Emoji provided by JoyPixels", "EmojiCustomFilesystem": "Custom Emoji Filesystem", "EmojiCustomFilesystem_Description": "Specify how emojis are stored.", + "Empty_no_agent_selected": "Empty, no agent selected", "Empty_title": "Empty title", "Use_Legacy_Message_Template": "Use legacy message template", "Use_Legacy_Message_Template_alert": "This is a deprecated feature. It may not work as expected and will not get new updates", diff --git a/apps/meteor/server/main.ts b/apps/meteor/server/main.ts index 78b6e52b4df6..5f0898049c6b 100644 --- a/apps/meteor/server/main.ts +++ b/apps/meteor/server/main.ts @@ -74,6 +74,7 @@ import './publications/spotlight'; import './publications/subscription'; import './routes/avatar'; import './routes/i18n'; +import './routes/timesync'; import './stream/stdout'; import './stream/streamBroadcast'; import './settings/index'; diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index 3a8fcd1ad2c2..7e419fe92a95 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -287,7 +287,7 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL } removeById(_id: string): Promise { - return this.removeById(_id); + return this.deleteOne({ _id }); } saveGuestEmailPhoneById(_id: string, emails: string[], phones: string[]): Promise { diff --git a/apps/meteor/server/routes/timesync.ts b/apps/meteor/server/routes/timesync.ts new file mode 100644 index 000000000000..b624d8acb85e --- /dev/null +++ b/apps/meteor/server/routes/timesync.ts @@ -0,0 +1,18 @@ +import { WebApp } from 'meteor/webapp'; +// Use rawConnectHandlers so we get a response as quickly as possible +// https://github.com/meteor/meteor/blob/devel/packages/webapp/webapp_server.js + +const syncUrl = `${(global as any)?.ROOT_URL_PATH_PREFIX || ''}/_timesync`; + +WebApp.rawConnectHandlers.use(syncUrl, function (_req, res) { + // Never ever cache this, otherwise weird times are shown on reload + // http://stackoverflow.com/q/18811286/586086 + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', 0); + + // Avoid MIME type warnings in browsers + res.setHeader('Content-Type', 'text/plain'); + + res.end(Date.now().toString()); +}); diff --git a/apps/meteor/server/startup/migrations/v278.ts b/apps/meteor/server/startup/migrations/v278.ts index 08a554bcb659..7b9ea045dee1 100644 --- a/apps/meteor/server/startup/migrations/v278.ts +++ b/apps/meteor/server/startup/migrations/v278.ts @@ -1,9 +1,38 @@ +import { Banners, Settings } from '@rocket.chat/models'; + import { addMigration } from '../../lib/migrations'; -import { upsertPermissions } from '../../../app/authorization/server/functions/upsertPermissions'; +import { isEnterprise } from '../../../ee/app/license/server'; +import { settings } from '../../../app/settings/server'; addMigration({ version: 278, - up() { - upsertPermissions(); + async up() { + const query = { + _id: { $in: [/^Accounts_OAuth_Custom-?([^-_]+)$/] }, + value: true, + }; + + const isCustomOAuthEnabled = !!(await Settings.findOne(query)); + const LDAPEnabled = settings.get('LDAP_Enable'); + const SAMLEnabled = settings.get('SAML_Custom_Default'); + + const isEE = isEnterprise(); + + if (!isEE && (isCustomOAuthEnabled || LDAPEnabled || SAMLEnabled)) { + return; + } + + await Banners.updateOne( + { + 'view.blocks.0.text.text': /authentication\-changes/, + 'active': { $ne: false }, + }, + { + $set: { + active: false, + inactivedAt: new Date(), + }, + }, + ); }, }); diff --git a/apps/meteor/tests/data/livechat/rooms.js b/apps/meteor/tests/data/livechat/rooms.js index aa3bd67f6feb..f915ee70bfa0 100644 --- a/apps/meteor/tests/data/livechat/rooms.js +++ b/apps/meteor/tests/data/livechat/rooms.js @@ -1,4 +1,4 @@ -import { api, credentials, request } from '../api-data'; +import { api, credentials, methodCall, request } from '../api-data'; import { adminUsername } from '../user'; export const createLivechatRoom = (visitorToken) => @@ -10,8 +10,11 @@ export const createLivechatRoom = (visitorToken) => }); export const createVisitor = () => - new Promise((resolve) => { - request.get(api('livechat/visitor/iNKE8a6k6cjbqWhWd')).end((err, res) => { + new Promise((resolve, reject) => { + const token = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + const email = `${token}@${token}.com`; + const phone = `${Math.floor(Math.random() * 10000000000)}`; + request.get(api(`livechat/visitor/${token}`)).end((err, res) => { if (!err && res && res.body && res.body.visitor) { return resolve(res.body.visitor); } @@ -21,36 +24,80 @@ export const createVisitor = () => .send({ visitor: { name: `Visitor ${Date.now()}`, - email: 'visitor@rocket.chat', - token: 'iNKE8a6k6cjbqWhWd', - phone: '55 51 5555-5555', + email, + token, + phone, customFields: [{ key: 'address', value: 'Rocket.Chat street', overwrite: true }], }, }) .end((err, res) => { + if (err) { + return reject(err); + } resolve(res.body.visitor); }); }); }); export const createAgent = () => - new Promise((resolve) => { + new Promise((resolve, reject) => { request .post(api('livechat/users/agent')) .set(credentials) .send({ username: adminUsername, }) - .end((err, res) => resolve(res.body.user)); + .end((err, res) => { + if (err) { + return reject(err); + } + resolve(res.body.user); + }); }); export const createManager = () => - new Promise((resolve) => { + new Promise((resolve, reject) => { request .post(api('livechat/users/manager')) .set(credentials) .send({ username: adminUsername, }) - .end((err, res) => resolve(res.body.user)); + .end((err, res) => { + if (err) { + return reject(err); + } + resolve(res.body.user); + }); }); + +export const makeAgentAvailable = () => + new Promise((resolve, reject) => { + request.post(api('users.setStatus')).set(credentials).send({ + message: '', + status: 'online', + }).end((err, res) => { + if (err) { + return reject(err); + } + request + .post(methodCall('livechat/changeLivechatStatus')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'livechat/changeLivechatStatus', + params: ['available'], + id: 'id', + msg: 'method', + }), + }) + .end((err, res) => { + if (err) { + return reject(err); + } + resolve(res.body); + }); + }); + + }); + diff --git a/apps/meteor/tests/e2e/00-wizard.spec.ts b/apps/meteor/tests/e2e/00-wizard.spec.ts index 80f2ed71d6a2..42c3d4d05f63 100644 --- a/apps/meteor/tests/e2e/00-wizard.spec.ts +++ b/apps/meteor/tests/e2e/00-wizard.spec.ts @@ -1,25 +1,195 @@ -import { test, expect, Page } from '@playwright/test'; +import { test, expect, Page, Locator } from '@playwright/test'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; -import { setupWizardStepRegex } from './utils/mocks/urlMock'; -import { HOME_SELECTOR } from './utils/mocks/waitSelectorsMock'; -import { LoginPage, SetupWizard } from './pageobjects'; +import { Auth } from './page-objects'; +import { ADMIN_CREDENTIALS } from './utils/constants'; + +class SetupWizard { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + 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('//a[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")]'); + } + + async stepTwoSuccess(): Promise { + await this.organizationName.type('rocket.chat.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(); + } + + async stepThreeSuccess(): Promise { + await this.standaloneServer.click(); + } + + 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(); + } + + async stepOneFailedWithInvalidEmail(adminCredentials: { name: string; password: string }): Promise { + await this.fullName.type(adminCredentials.name); + await this.userName.type(adminCredentials.name); + await this.companyEmail.type('mail'); + await this.password.type(adminCredentials.password); + await this.nextStep.click(); + await expect(this.companyEmail).toBeFocused(); + } + + 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(); + } + + async stepThreeFailedWithInvalidField(): Promise { + await this.registeredServer.type('mail'); + await this.registeredServer.click({ clickCount: 3 }); + await this.page.keyboard.press('Backspace'); + await expect(this.stepThreeInputInvalidMail).toBeVisible(); + } +} test.describe('[Wizard]', () => { - let setupWizard: SetupWizard; - let loginPage: LoginPage; let page: Page; + let pageAuth: Auth; + + let setupWizard: SetupWizard; test.beforeEach(async ({ browser }) => { page = await browser.newPage(); + pageAuth = new Auth(page); setupWizard = new SetupWizard(page); - loginPage = new LoginPage(page); }); test.describe('[Step 2]', async () => { test.beforeEach(async () => { - await page.goto('/'); - await loginPage.doLogin(adminLogin, false); + await pageAuth.doLogin(ADMIN_CREDENTIALS, false); }); test('expect required field alert showed when user dont inform data', async () => { @@ -28,14 +198,13 @@ test.describe('[Wizard]', () => { test('expect go to Step 3 successfully', async () => { await setupWizard.stepTwoSuccess(); - await expect(setupWizard.page).toHaveURL(setupWizardStepRegex._3); + await expect(page).toHaveURL(/.*\/setup-wizard\/3/); }); }); test.describe('[Step 3]', async () => { test.beforeEach(async () => { - await page.goto(''); - await loginPage.doLogin(adminLogin, false); + await pageAuth.doLogin(ADMIN_CREDENTIALS, false); await setupWizard.stepTwoSuccess(); }); @@ -64,8 +233,7 @@ test.describe('[Wizard]', () => { test.describe('[Final Step]', async () => { test.beforeEach(async () => { - await page.goto(''); - await loginPage.doLogin(adminLogin, false); + await pageAuth.doLogin(ADMIN_CREDENTIALS, false); await setupWizard.stepTwoSuccess(); await setupWizard.stepThreeSuccess(); }); @@ -77,7 +245,8 @@ test.describe('[Wizard]', () => { test('expect confirm standalone', async () => { await setupWizard.goToWorkspace.click(); - await page.waitForSelector(HOME_SELECTOR); + // HOME_SELECTOR + await page.waitForSelector('//span[@class="rc-header__block"]'); }); }); }); diff --git a/apps/meteor/tests/e2e/01-forgot-password.spec.ts b/apps/meteor/tests/e2e/01-forgot-password.spec.ts index b8b29e1575e9..90366bd2163f 100644 --- a/apps/meteor/tests/e2e/01-forgot-password.spec.ts +++ b/apps/meteor/tests/e2e/01-forgot-password.spec.ts @@ -1,40 +1,45 @@ -import { test, expect } from '@playwright/test'; +import { Page, test, expect } from '@playwright/test'; -import { Global, LoginPage } from './pageobjects'; +import { Auth } from './page-objects'; -test.describe('[Forgot Password]', () => { - let loginPage: LoginPage; - let global: Global; +test.describe('Forgot Password', () => { + let page: Page; + let pageAuth: Auth; - test.beforeEach(async ({ page }) => { - loginPage = new LoginPage(page); - global = new Global(page); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + pageAuth = new Auth(page); + }); + test.beforeAll(async () => { await page.goto('/'); - await loginPage.btnForgotPassword.click(); + await pageAuth.btnForgotPassword.click(); }); - test('expect be required', async () => { - loginPage.btnSubmit.click(); + test('expect trigger a validation error if no email is provided', async () => { + await pageAuth.btnSubmit.click(); - await expect(loginPage.emailInvalidText).toBeVisible(); + await expect(pageAuth.textErrorEmail).toBeVisible(); }); - test('expect invalid for email without domain', async () => { - await loginPage.emailField.type('mail'); - await loginPage.btnSubmit.click(); - await expect(loginPage.emailInvalidText).toBeVisible(); + test('expect trigger a validation if a invalid email is provided (1)', async () => { + await pageAuth.inputEmail.type('mail'); + await pageAuth.btnSubmit.click(); + + await expect(pageAuth.textErrorEmail).toBeVisible(); }); - test('expect be invalid for email with invalid domain', async () => { - await loginPage.emailField.type('mail@mail'); - await loginPage.btnSubmit.click(); - await expect(loginPage.emailInvalidText).toBeVisible(); + test('expect trigger a validation if a invalid email is provided (2)', async () => { + await pageAuth.inputEmail.type('mail@mail'); + await pageAuth.btnSubmit.click(); + + await expect(pageAuth.textErrorEmail).toBeVisible(); }); - test('expect user type a valid email', async () => { - await loginPage.emailField.type('mail@mail.com'); - await loginPage.btnSubmit.click(); - await expect(global.getToastBarSuccess).toBeVisible(); + test('expect to show a success toast if a valid email is provided', async () => { + await pageAuth.inputEmail.type('mail@mail.com'); + await pageAuth.btnSubmit.click(); + + await expect(pageAuth.toastSuccess).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/02-register.spec.ts b/apps/meteor/tests/e2e/02-register.spec.ts index 7a5c2cecac8e..fd9d764e4d68 100644 --- a/apps/meteor/tests/e2e/02-register.spec.ts +++ b/apps/meteor/tests/e2e/02-register.spec.ts @@ -1,39 +1,46 @@ -import { test, expect } from '@playwright/test'; +import { Page, test, expect } from '@playwright/test'; +import { faker } from '@faker-js/faker'; -import { registerUser } from './utils/mocks/userAndPasswordMock'; -import { LoginPage } from './pageobjects'; +import { Auth } from './page-objects'; -test.describe('[Register]', () => { - let loginPage: LoginPage; +test.describe('Register', () => { + let page: Page; + let pageAuth: Auth; - test.beforeEach(async ({ page }) => { - loginPage = new LoginPage(page); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + pageAuth = new Auth(page); + }); + + test.beforeEach(async () => { await page.goto('/'); + await pageAuth.btnRegister.click(); }); - test('expect user click in register button without data', async () => { - await loginPage.btnRegister.click(); - await loginPage.btnSubmit.click(); + test('expect trigger a validation error if no data is provided', async () => { + await pageAuth.btnSubmit.click(); - await expect(loginPage.nameInvalidText).toBeVisible(); - await expect(loginPage.emailInvalidText).toBeVisible(); - await expect(loginPage.passwordInvalidText).toBeVisible(); + await expect(pageAuth.textErrorName).toBeVisible(); + await expect(pageAuth.textErrorEmail).toBeVisible(); + await expect(pageAuth.textErrorPassword).toBeVisible(); }); - test('expect user click in register button with different password', async () => { - await loginPage.btnRegister.click(); - await loginPage.passwordField.type(registerUser.password); - await loginPage.emailField.type(registerUser.email); - await loginPage.nameField.type(registerUser.name); - await loginPage.confirmPasswordField.type('wrong_password'); + test('expect trigger a validation error if different password is provided', async () => { + await pageAuth.inputName.type(faker.name.firstName()); + await pageAuth.inputEmail.type(faker.internet.email()); + await pageAuth.inputPassword.type('any_password'); + await pageAuth.inputPasswordConfirm.type('any_password_2'); + await pageAuth.btnSubmit.click(); - await loginPage.btnSubmit.click(); - await expect(loginPage.confirmPasswordInvalidText).toBeVisible(); - await expect(loginPage.confirmPasswordInvalidText).toHaveText('The password confirmation does not match password'); + await expect(pageAuth.textErrorPasswordConfirm).toBeVisible(); }); - test('expect new user is created', async () => { - await loginPage.btnRegister.click(); - await loginPage.registerNewUser(registerUser); + test('expect successfully register a new user', async () => { + await pageAuth.inputName.type(faker.name.firstName()); + await pageAuth.inputEmail.type(faker.internet.email()); + await pageAuth.inputPassword.type('any_password'); + await pageAuth.inputPasswordConfirm.type('any_password'); + await pageAuth.btnSubmit.click(); + await pageAuth.btnRegisterConfirmUsername.click(); }); }); diff --git a/apps/meteor/tests/e2e/03-login.spec.ts b/apps/meteor/tests/e2e/03-login.spec.ts index 0eb5b4ac0694..38c156ed1b74 100644 --- a/apps/meteor/tests/e2e/03-login.spec.ts +++ b/apps/meteor/tests/e2e/03-login.spec.ts @@ -1,32 +1,24 @@ -import { test, expect, Page } from '@playwright/test'; +import { Page, test, expect } from '@playwright/test'; +import { faker } from '@faker-js/faker'; -import { validUser } from './utils/mocks/userAndPasswordMock'; -import { Global, LoginPage } from './pageobjects'; -import { HOME_SELECTOR } from './utils/mocks/waitSelectorsMock'; +import { Auth } from './page-objects'; -test.describe('[Login]', () => { +test.describe('Login', () => { let page: Page; - let loginPage: LoginPage; - let global: Global; + let pageAuth: Auth; - test.beforeEach(async ({ browser }) => { + test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - loginPage = new LoginPage(page); - global = new Global(page); - await page.goto('/'); + pageAuth = new Auth(page); }); - test('expect user write a password incorrectly', async () => { - const invalidUserPassword = { - email: validUser.email, - password: 'any_password1', - }; - await loginPage.doLogin(invalidUserPassword, false); - await expect(global.getToastBarError).toBeVisible(); - }); + test('expect to show a toast if the provided password is incorrect', async () => { + await page.goto('/'); + + await pageAuth.inputEmailOrUsername.type(faker.internet.email()); + await pageAuth.inputPassword.type('any_password'); + await pageAuth.btnSubmit.click(); - test('expect user make login', async () => { - await loginPage.doLogin(validUser); - await page.waitForSelector(HOME_SELECTOR); + await expect(pageAuth.toastError).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/04-main-elements-render.spec.ts b/apps/meteor/tests/e2e/04-main-elements-render.spec.ts deleted file mode 100644 index 93e1fb714536..000000000000 --- a/apps/meteor/tests/e2e/04-main-elements-render.spec.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { test, expect, Page } from '@playwright/test'; - -import { LoginPage, FlexTab, SideNav, MainContent } from './pageobjects'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; - -test.describe('[Main Elements Render]', function () { - let page: Page; - let loginPage: LoginPage; - let mainContent: MainContent; - let sideNav: SideNav; - let flexTab: FlexTab; - - test.beforeAll(async ({ browser }) => { - page = await browser.newPage(); - - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - mainContent = new MainContent(page); - flexTab = new FlexTab(page); - - await page.goto('/'); - await loginPage.doLogin(adminLogin); - }); - - test.describe('[Side Nav Bar]', () => { - test.describe('[Render]', () => { - test('expect show the new channel button', async () => { - await expect(sideNav.btnSidebarCreate).toBeVisible(); - }); - - test('expect show "general" channel', async () => { - 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(); - }); - - test('expect click the spotlight and show the channel list', async () => { - 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 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(); - }); - - test.afterEach(async () => { - await sideNav.sidebarUserMenu.click(); - }); - - test('expect show online button', async () => { - await expect(sideNav.statusOnline).toBeVisible(); - }); - - test('expect show away button', async () => { - await expect(sideNav.statusAway).toBeVisible(); - }); - - test('expect show busy button', async () => { - await expect(sideNav.statusBusy).toBeVisible(); - }); - - test('expect show offline button', async () => { - await expect(sideNav.statusOffline).toBeVisible(); - }); - - test('expect show my account button', async () => { - await expect(sideNav.account).toBeVisible(); - }); - - test('expect show logout button', async () => { - await expect(sideNav.logout).toBeVisible(); - }); - }); - }); - - test.describe('[Main Content]', () => { - test.describe('[Render]', () => { - test.beforeAll(async () => { - await sideNav.doOpenChat('general'); - }); - - test('expect show the title of the channel', async () => { - await expect(mainContent.channelTitle('general')).toBeVisible(); - }); - - test('expect show the empty favorite star (before)', async () => { - await expect(mainContent.emptyFavoriteStar).toBeVisible(); - }); - - test('expect click the empty star', async () => { - await mainContent.emptyFavoriteStar.click(); - }); - - test('expect show the filled favorite star', async () => { - await expect(mainContent.favoriteStar).toBeVisible(); - }); - - test('expect click the star', async () => { - await mainContent.favoriteStar.click(); - }); - - test('expect show the empty favorite star (after)', async () => { - await expect(mainContent.emptyFavoriteStar).toBeVisible(); - }); - - test('expect show the message input bar', async () => { - await expect(mainContent.messageInput).toBeVisible(); - }); - - test('expect show the message box actions button', async () => { - await expect(mainContent.messageBoxActions).toBeVisible(); - }); - - test('expect show the audio recording button', async () => { - await expect(mainContent.recordBtn).toBeVisible(); - }); - - test('expect show the emoji button', async () => { - await expect(mainContent.emojiBtn).toBeVisible(); - }); - test('expect not show the Admin tag', async () => { - await expect(mainContent.lastMessageUserTag).not.toBeVisible(); - }); - }); - }); - - test.describe('[FlexTab]', () => { - test.describe('[Render]', () => { - test.beforeAll(async () => { - await sideNav.doOpenChat('general'); - }); - - test('expect to show tab info content', async () => { - await flexTab.btnTabInfo.click(); - await expect(flexTab.contentTabInfo).toBeVisible(); - await flexTab.btnTabInfo.click(); - }); - - test('expect to show tab thread content', async () => { - await flexTab.btnTabSearch.click(); - await expect(flexTab.contentTabSearch).toBeVisible(); - await flexTab.btnTabSearch.click(); - }); - - test('expect to show tab members content', async () => { - await flexTab.btnTabMembers.click(); - await expect(flexTab.contentTabMembers).toBeVisible(); - await flexTab.btnTabMembers.click(); - }); - - test('expect to show tab notifications content', async () => { - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabNotifications.click(); - - await expect(flexTab.contentTabNotifications).toBeVisible(); - - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabNotifications.click(); - }); - - test('expect to show tab files content', async () => { - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabFiles.click(); - - await expect(flexTab.contentTabFiles).toBeVisible(); - - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabFiles.click(); - }); - - test('expect to show tab mentions content', async () => { - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabMentions.click(); - - await expect(flexTab.contentTabMentions).toBeVisible(); - - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabMentions.click(); - }); - - test('expect to show tab stared content', async () => { - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabStared.click(); - - await expect(flexTab.contentTabStared).toBeVisible(); - - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabStared.click(); - }); - - test('expect to show tab pinned content', async () => { - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabPinned.click(); - - await expect(flexTab.contentTabPinned).toBeVisible(); - - await flexTab.doOpenMoreOptionMenu(); - await flexTab.btnTabPinned.click(); - }); - }); - }); -}); diff --git a/apps/meteor/tests/e2e/05-channel-creation.spec.ts b/apps/meteor/tests/e2e/05-channel-creation.spec.ts index ed1e3a65f8fa..ced385f4ef6c 100644 --- a/apps/meteor/tests/e2e/05-channel-creation.spec.ts +++ b/apps/meteor/tests/e2e/05-channel-creation.spec.ts @@ -1,27 +1,43 @@ -import { test } from '@playwright/test'; +import { Page, test, expect } from '@playwright/test'; import { faker } from '@faker-js/faker'; -import { LoginPage, SideNav } from './pageobjects'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; +import { HomeChannel, Auth } from './page-objects'; -test.describe('[Channel]', async () => { - let sideNav: SideNav; - let loginPage: LoginPage; +test.describe('Channel Creation', () => { + let page: Page; + let pageAuth: Auth; + let pageHomeChannel: HomeChannel; test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - - await page.goto('/'); - await loginPage.doLogin(adminLogin); + page = await browser.newPage(); + pageAuth = new Auth(page); + pageHomeChannel = new HomeChannel(page); }); - test('expect create private channel', async () => { - await sideNav.doCreateChannel(faker.animal.type() + Date.now(), true); + test.beforeAll(async () => { + await pageAuth.doLogin(); }); test('expect create public channel', async () => { - await sideNav.doCreateChannel(faker.animal.type() + Date.now()); + const name = faker.animal.type() + Date.now(); + + await pageHomeChannel.sidenav.btnCreate.click(); + await pageHomeChannel.sidenav.createOptionByText('Channel').click(); + await pageHomeChannel.sidenav.checkboxChannelType.click(); + await pageHomeChannel.sidenav.inputChannelName.type(name); + await pageHomeChannel.sidenav.btnCreateChannel.click(); + + await expect(page).toHaveURL(`/channel/${name}`); + }); + + test('expect create private channel', async () => { + const name = faker.animal.type() + Date.now(); + + await pageHomeChannel.sidenav.btnCreate.click(); + await pageHomeChannel.sidenav.createOptionByText('Channel').click(); + await pageHomeChannel.sidenav.inputChannelName.type(name); + await pageHomeChannel.sidenav.btnCreateChannel.click(); + + await expect(page).toHaveURL(`/group/${name}`); }); }); diff --git a/apps/meteor/tests/e2e/06-messaging.spec.ts b/apps/meteor/tests/e2e/06-messaging.spec.ts index c6ca396b1649..4ce3b1938743 100644 --- a/apps/meteor/tests/e2e/06-messaging.spec.ts +++ b/apps/meteor/tests/e2e/06-messaging.spec.ts @@ -1,275 +1,191 @@ -import { expect, test, Browser } from '@playwright/test'; - -import { FlexTab, MainContent, SideNav, LoginPage } from './pageobjects'; -import { adminLogin, validUserInserted } from './utils/mocks/userAndPasswordMock'; - -const createBrowserContextForChat = async ( - browser: Browser, - baseURL: string, -): Promise<{ - mainContent: MainContent; - sideNav: SideNav; -}> => { - const page = await browser.newPage(); +import { expect, test, Browser, Page } from '@playwright/test'; - const loginPage = new LoginPage(page); - const mainContent = new MainContent(page); - const sideNav = new SideNav(page); +import { validUserInserted } from './utils/mocks/userAndPasswordMock'; +import { Auth, HomeChannel } from './page-objects'; - await page.goto(baseURL); - await loginPage.doLogin(validUserInserted); +const createBrowserContextForChat = async (browser: Browser): Promise<{ page: Page; pageHomeChannel: HomeChannel }> => { + const page = await browser.newPage(); + const pageLogin = new Auth(page); + const pageHomeChannel = new HomeChannel(page); + await pageLogin.doLogin(validUserInserted); - return { mainContent, sideNav }; + return { page, pageHomeChannel }; }; -test.describe('[Messaging]', () => { - let loginPage: LoginPage; - let mainContent: MainContent; - let sideNav: SideNav; - let flexTab: FlexTab; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); +test.describe('Messaging', () => { + let page: Page; + let pageLogin: Auth; + let pageHomeChannel: HomeChannel; - loginPage = new LoginPage(page); - mainContent = new MainContent(page); - sideNav = new SideNav(page); - flexTab = new FlexTab(page); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + pageLogin = new Auth(page); + pageHomeChannel = new HomeChannel(page); + }); - await page.goto('/'); - await loginPage.doLogin(adminLogin); + test.beforeAll(async () => { + await pageLogin.doLogin(); }); - test.describe('[Normal messaging]', async () => { - 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.mainContent.sendMessage('Hello'); - await sideNav.general.click(); - await mainContent.sendMessage('Hello'); - }); - test.afterAll(async () => { - await anotherContext.mainContent.page.close(); + test.describe('Normal messaging', async () => { + let anotherContext: { page: Page; pageHomeChannel: HomeChannel }; + + test.describe('General channel', async () => { + test.beforeAll(async ({ browser }) => { + anotherContext = await createBrowserContextForChat(browser); + await anotherContext.pageHomeChannel.sidenav.doOpenChat('general'); + await anotherContext.pageHomeChannel.content.doSendMessage('Hello'); + await pageHomeChannel.sidenav.doOpenChat('general'); + await pageHomeChannel.content.doSendMessage('Hello'); }); + test('expect received message is visible for two context', async () => { - const anotherUserMessage = mainContent.page.locator('[data-qa-type="message"][data-own="false"]').last(); - const mainUserMessage = anotherContext.mainContent.page.locator('[data-qa-type="message"][data-own="true"]').last(); + const anotherUserMessage = page.locator('[data-qa-type="message"][data-own="false"]').last(); + const mainUserMessage = anotherContext.page.locator('[data-qa-type="message"][data-own="false"]').last(); await expect(anotherUserMessage).toBeVisible(); await expect(mainUserMessage).toBeVisible(); }); }); - test.describe('[Public channel]', async () => { - test.beforeAll(async ({ browser, baseURL }) => { - anotherContext = await createBrowserContextForChat(browser, baseURL as string); - await anotherContext.sideNav.doOpenChat('public channel'); - await anotherContext.mainContent.sendMessage('Hello'); - await sideNav.doOpenChat('public channel'); - await mainContent.sendMessage('Hello'); - }); - test.afterAll(async () => { - await anotherContext.mainContent.page.close(); + test.describe('Public channel', async () => { + test.beforeAll(async ({ browser }) => { + anotherContext = await createBrowserContextForChat(browser); + await anotherContext.pageHomeChannel.sidenav.doOpenChat('public channel'); + await anotherContext.pageHomeChannel.content.doSendMessage('Hello'); + await pageHomeChannel.sidenav.doOpenChat('public channel'); + await pageHomeChannel.content.doSendMessage('Hello'); }); + test('expect received message is visible for two context', async () => { - const anotherUserMessage = mainContent.page.locator('[data-qa-type="message"][data-own="false"]').last(); - const mainUserMessage = anotherContext.mainContent.page.locator('[data-qa-type="message"][data-own="true"]').last(); + const anotherUserMessage = page.locator('[data-qa-type="message"][data-own="false"]').last(); + const mainUserMessage = anotherContext.page.locator('[data-qa-type="message"][data-own="false"]').last(); await expect(anotherUserMessage).toBeVisible(); await expect(mainUserMessage).toBeVisible(); }); }); - test.describe('[Private channel]', async () => { - test.beforeAll(async ({ browser, baseURL }) => { - anotherContext = await createBrowserContextForChat(browser, baseURL as string); - await anotherContext.sideNav.doOpenChat('private channel'); - await anotherContext.mainContent.sendMessage('Hello'); - await sideNav.doOpenChat('private channel'); - await mainContent.sendMessage('Hello'); - }); - test.afterAll(async () => { - await anotherContext.mainContent.page.close(); + test.describe('Private channel', async () => { + test.beforeAll(async ({ browser }) => { + anotherContext = await createBrowserContextForChat(browser); + await anotherContext.pageHomeChannel.sidenav.doOpenChat('private channel'); + await anotherContext.pageHomeChannel.content.doSendMessage('Hello'); + await pageHomeChannel.sidenav.doOpenChat('private channel'); + await pageHomeChannel.content.doSendMessage('Hello'); }); + test('expect received message is visible for two context', async () => { - const anotherUserMessage = mainContent.page.locator('[data-qa-type="message"][data-own="false"]').last(); - const mainUserMessage = anotherContext.mainContent.page.locator('[data-qa-type="message"][data-own="true"]').last(); + const anotherUserMessage = page.locator('[data-qa-type="message"][data-own="false"]').last(); + const mainUserMessage = anotherContext.page.locator('[data-qa-type="message"][data-own="false"]').last(); await expect(anotherUserMessage).toBeVisible(); await expect(mainUserMessage).toBeVisible(); }); }); - test.describe('[Direct Message]', async () => { - test.beforeAll(async ({ browser, baseURL }) => { - anotherContext = await createBrowserContextForChat(browser, baseURL as string); - await anotherContext.sideNav.doOpenChat('rocketchat.internal.admin.test'); - await anotherContext.mainContent.sendMessage('Hello'); - await sideNav.doOpenChat('user.name.test'); - await mainContent.sendMessage('Hello'); - }); - test.afterAll(async () => { - await anotherContext.mainContent.page.close(); + test.describe('Direct Message', async () => { + test.beforeAll(async ({ browser }) => { + anotherContext = await createBrowserContextForChat(browser); + await anotherContext.pageHomeChannel.sidenav.doOpenChat('rocketchat.internal.admin.test'); + await anotherContext.pageHomeChannel.content.doSendMessage('Hello'); + await pageHomeChannel.sidenav.doOpenChat('user.name.test'); + await pageHomeChannel.content.doSendMessage('Hello'); }); + test('expect received message is visible for two context', async () => { - const anotherUserMessage = mainContent.page.locator('[data-qa-type="message"][data-own="false"]').last(); - const mainUserMessage = anotherContext.mainContent.page.locator('[data-qa-type="message"][data-own="false"]').last(); + const anotherUserMessage = page.locator('[data-qa-type="message"][data-own="false"]').last(); + const mainUserMessage = anotherContext.page.locator('[data-qa-type="message"][data-own="false"]').last(); await expect(anotherUserMessage).toBeVisible(); await expect(mainUserMessage).toBeVisible(); }); }); - test.describe('[File Upload]', async () => { + test.describe('File Upload', async () => { test.beforeAll(async () => { - await sideNav.general.click(); + await pageHomeChannel.sidenav.doOpenChat('general'); + }); + + test.beforeEach(async () => { + await pageHomeChannel.content.doDragAndDropFile(); }); - test.describe('[Render]', async () => { - test.beforeAll(async () => { - await mainContent.dragAndDropFile(); - }); - test('expect modal is visible', async () => { - await expect(mainContent.modalTitle).toHaveText('File Upload'); - }); - test('expect cancel button is visible', async () => { - await expect(mainContent.modalCancelButton).toBeVisible(); - }); - test('expect confirm button is visible', async () => { - await expect(mainContent.buttonSend).toBeVisible(); - }); - test('expect file preview is visible', async () => { - 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'); - }); - - test('expect file description is visible', async () => { - await expect(mainContent.fileDescription).toBeVisible(); - await expect(mainContent.fileDescription).toHaveText('File description'); - }); + + test('expect not show modal after click in cancel button', async () => { + await pageHomeChannel.content.modalCancelButton.click(); + await expect(pageHomeChannel.content.modalFilePreview).not.toBeVisible(); }); - test.describe('[Actions]', async () => { - test.beforeEach(async () => { - await mainContent.dragAndDropFile(); - }); - - test('expect not show modal after click in cancel button', async () => { - 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(); - }); - test('expect send file with description', async () => { - await mainContent.setDescription(); - await mainContent.sendFileClick(); - 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'); - }); + + test('expect send file not show modal', async () => { + await pageHomeChannel.content.buttonSend.click(); + await expect(pageHomeChannel.content.modalFilePreview).not.toBeVisible(); + }); + + test('expect send file with description', async () => { + await pageHomeChannel.content.descriptionInput.type('any_description'); + await pageHomeChannel.content.buttonSend.click(); + await expect(pageHomeChannel.content.getFileDescription).toHaveText('any_description'); + }); + + test('expect send file with different file name', async () => { + await pageHomeChannel.content.fileNameInput.type('any_file1.txt'); + await pageHomeChannel.content.buttonSend.click(); + await expect(pageHomeChannel.content.lastMessageFileName).toContainText('any_file1.txt'); }); }); - test.describe('[Messaging actions]', async () => { - test.describe('[Usage]', async () => { - test.beforeAll(async () => { - await sideNav.general.click(); - }); - test.describe('[Reply]', async () => { - test.beforeAll(async () => { - await mainContent.sendMessage('This is a message for reply'); - await mainContent.openMessageActionMenu(); - }); - test('expect reply the message', async () => { - await mainContent.selectAction('reply'); - 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(); - }); - }); - - test.describe('[Edit]', async () => { - test.beforeAll(async () => { - await mainContent.sendMessage('This is a message for edit'); - await mainContent.openMessageActionMenu(); - }); - - test('expect edit the message', async () => { - await mainContent.selectAction('edit'); - }); - }); - - test.describe('[Delete]', async () => { - test.beforeAll(async () => { - await mainContent.sendMessage('Message for Message Delete Tests'); - await mainContent.openMessageActionMenu(); - }); - - test('expect message is deleted', async () => { - await mainContent.selectAction('delete'); - }); - }); - - test.describe('[Quote]', async () => { - const message = `Message for quote Tests - ${Date.now()}`; - - test.beforeAll(async () => { - await mainContent.sendMessage(message); - await mainContent.openMessageActionMenu(); - }); - - test('it should quote the message', async () => { - await mainContent.selectAction('quote'); - await expect(mainContent.waitForLastMessageTextAttachmentEqualsText).toHaveText(message); - }); - }); - - test.describe('[Star]', async () => { - test.beforeAll(async () => { - await mainContent.sendMessage('Message for star Tests'); - await mainContent.openMessageActionMenu(); - }); - - test('it should star the message', async () => { - await mainContent.selectAction('star'); - }); - }); - - test.describe('[Copy]', async () => { - test.beforeAll(async () => { - await mainContent.sendMessage('Message for copy Tests'); - await mainContent.openMessageActionMenu(); - }); - - test('it should copy the message', async () => { - await mainContent.selectAction('copy'); - }); - }); - - test.describe('[Permalink]', async () => { - test.beforeAll(async () => { - await mainContent.sendMessage('Message for permalink Tests'); - await mainContent.openMessageActionMenu(); - }); - - test('it should permalink the message', async () => { - await mainContent.selectAction('permalink'); - }); - }); + test.describe('Messaging actions', async () => { + test.beforeAll(async () => { + await pageHomeChannel.sidenav.doOpenChat('general'); + }); + + test('expect reply the message', async () => { + await pageHomeChannel.content.doSendMessage('This is a message for reply'); + await pageHomeChannel.content.doOpenMessageActionMenu(); + await pageHomeChannel.content.doSelectAction('reply'); + await pageHomeChannel.tabs.messageInput.type('this is a reply message'); + await page.keyboard.press('Enter'); + await expect(pageHomeChannel.tabs.flexTabViewThreadMessage).toHaveText('this is a reply message'); + await pageHomeChannel.tabs.closeThreadMessage.click(); + }); + + test('expect edit the message', async () => { + await pageHomeChannel.content.doSendMessage('This is a message to edit'); + await pageHomeChannel.content.doOpenMessageActionMenu(); + await pageHomeChannel.content.doSelectAction('edit'); + }); + + test('expect message is deleted', async () => { + await pageHomeChannel.content.doSendMessage('Message to delete'); + await pageHomeChannel.content.doOpenMessageActionMenu(); + await pageHomeChannel.content.doSelectAction('delete'); + }); + + test('it should quote the message', async () => { + const message = `Message for quote - ${Date.now()}`; + + await pageHomeChannel.content.doSendMessage(message); + await pageHomeChannel.content.doOpenMessageActionMenu(); + await pageHomeChannel.content.doSelectAction('quote'); + + await expect(pageHomeChannel.content.waitForLastMessageTextAttachmentEqualsText).toHaveText(message); + }); + + test('it should star the message', async () => { + await pageHomeChannel.content.doSendMessage('Message to star'); + await pageHomeChannel.content.doOpenMessageActionMenu(); + await pageHomeChannel.content.doSelectAction('star'); + }); + + test('it should copy the message', async () => { + await pageHomeChannel.content.doSendMessage('Message to copy'); + await pageHomeChannel.content.doOpenMessageActionMenu(); + await pageHomeChannel.content.doSelectAction('copy'); + }); + + test('it should permalink the message', async () => { + await pageHomeChannel.content.doSendMessage('Message to permalink'); + await pageHomeChannel.content.doOpenMessageActionMenu(); + await pageHomeChannel.content.doSelectAction('permalink'); }); }); }); diff --git a/apps/meteor/tests/e2e/07-emoji.spec.ts b/apps/meteor/tests/e2e/07-emoji.spec.ts index 649c4bf72aad..3ad8ed594e44 100644 --- a/apps/meteor/tests/e2e/07-emoji.spec.ts +++ b/apps/meteor/tests/e2e/07-emoji.spec.ts @@ -1,155 +1,90 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, Page } from '@playwright/test'; -import { SideNav, MainContent, LoginPage } from './pageobjects'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; +import { Auth, HomeChannel } from './page-objects'; -test.describe('[Emoji]', () => { - let loginPage: LoginPage; - let mainContent: MainContent; - let sideNav: SideNav; +test.describe('Emoji', () => { + let page: Page; + let pageAuth: Auth; + let pageHomeChannel: HomeChannel; test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - mainContent = new MainContent(page); - - await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.doOpenChat('general'); + page = await browser.newPage(); + pageAuth = new Auth(page); + pageHomeChannel = new HomeChannel(page); }); - test.describe('Render:', () => { - test.beforeAll(async () => { - await mainContent.emojiBtn.click(); - }); - - test.afterAll(async () => { - await mainContent.emojiSmile.first().click(); - await mainContent.setTextToInput(''); - }); + test.beforeAll(async () => { + await pageAuth.doLogin(); + await pageHomeChannel.sidenav.doOpenChat('general'); + }); - test('expect show the emoji picker menu', async () => { - await expect(mainContent.emojiPickerMainScreen).toBeVisible(); + test.describe('send emoji via screen:', () => { + test.beforeAll(async () => { + await pageHomeChannel.content.emojiBtn.click(); + await pageHomeChannel.content.emojiPickerPeopleIcon.click(); }); - test('expect click the emoji picker people tab', async () => { - await mainContent.emojiPickerPeopleIcon.click(); + test('expect select a grinning emoji', async () => { + await pageHomeChannel.content.emojiGrinning.first().click(); }); - test('expect show the emoji picker people tab', async () => { - await expect(mainContent.emojiPickerPeopleIcon).toBeVisible(); + test('expect be that the value on the message input is the same as the emoji clicked', async () => { + await expect(pageHomeChannel.content.inputMain).toHaveValue(':grinning: '); }); - test('expect show the emoji picker nature tab', async () => { - await expect(mainContent.emojiPickerNatureIcon).toBeVisible(); + test('expect send the emoji', async () => { + await pageHomeChannel.content.inputMain.type(' '); + await page.keyboard.press('Enter'); }); - test('expect show the emoji picker food tab', async () => { - await expect(mainContent.emojiPickerFoodIcon).toBeVisible(); + test('expect be that the value on the message is the same as the emoji clicked', async () => { + await expect(pageHomeChannel.content.lastUserMessage).toContainText('😀'); }); + }); - test('expect show the emoji picker activity tab', async () => { - await expect(mainContent.emojiPickerActivityIcon).toBeVisible(); + test.describe('send emoji via text:', () => { + test('expect add emoji text to the message input', async () => { + await pageHomeChannel.content.inputMain.type(':smiley'); }); - test('expect show the emoji picker travel tab', async () => { - await expect(mainContent.emojiPickerTravelIcon).toBeVisible(); + test('expect show the emoji popup bar', async () => { + await expect(pageHomeChannel.content.messagePopUp).toBeVisible(); }); - test('expect show the emoji picker objects tab', async () => { - await expect(mainContent.emojiPickerObjectsIcon).toBeVisible(); + test('expect be that the emoji popup bar title is emoji', async () => { + await expect(pageHomeChannel.content.messagePopUpTitle).toContainText('Emoji'); }); - test('expect show the emoji picker symbols tab', async () => { - await expect(mainContent.emojiPickerSymbolsIcon).toBeVisible(); + test('expect show the emoji popup bar items', async () => { + await expect(pageHomeChannel.content.messagePopUpItems).toBeVisible(); }); - test('expect show the emoji picker flags tab', async () => { - await expect(mainContent.emojiPickerFlagsIcon).toBeVisible(); + test('expect click the first emoji on the popup list', async () => { + await pageHomeChannel.content.messagePopUpFirstItem.click(); }); - test('expect show the emoji picker custom tab', async () => { - await expect(mainContent.emojiPickerCustomIcon).toBeVisible(); + test('expect be that the value on the message input is the same as the emoji clicked', async () => { + await expect(pageHomeChannel.content.inputMain).toHaveValue(':smiley: '); }); - test('expect show the emoji picker change tone button', async () => { - await expect(mainContent.emojiPickerChangeTone).toBeVisible(); + test('expect send the emoji', async () => { + await pageHomeChannel.content.btnSend.click(); }); - test('expect show the emoji picker search bar', async () => { - await expect(mainContent.emojiPickerFilter).toBeVisible(); + test('expect be that the value on the message is the same as the emoji clicked', async () => { + await expect(pageHomeChannel.content.lastUserMessage).toContainText('😃'); }); }); - test.describe('[Usage]', () => { - test.describe('send emoji via screen:', () => { - test.beforeAll(async () => { - await mainContent.emojiBtn.click(); - await mainContent.emojiPickerPeopleIcon.click(); - }); - - test('expect select a grinning emoji', async () => { - 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: '); - }); - - test('expect send the emoji', async () => { - await mainContent.addTextToInput(' '); - 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('😀'); - }); - }); - - test.describe('send emoji via text:', () => { - test('expect add emoji text to the message input', async () => { - await mainContent.addTextToInput(':smiley'); - }); - - test('expect show the emoji popup bar', async () => { - await expect(mainContent.messagePopUp).toBeVisible(); - }); - - test('expect be that the emoji popup bar title is emoji', async () => { - await expect(mainContent.messagePopUpTitle).toContainText('Emoji'); - }); - - test('expect show the emoji popup bar items', async () => { - await expect(mainContent.messagePopUpItems).toBeVisible(); - }); - - test('expect click the first emoji on the popup list', async () => { - 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: '); - }); - - test('expect send the emoji', async () => { - 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('😃'); - }); + test.describe("send texts and make sure they're not converted to emojis:", () => { + test('should render numbers', async () => { + await pageHomeChannel.content.doSendMessage('0 1 2 3 4 5 6 7 8 9'); + await expect(pageHomeChannel.content.lastUserMessage).toContainText('0 1 2 3 4 5 6 7 8 9'); }); - test.describe("send texts and make sure they're not converted to emojis:", () => { - test('should render numbers', async () => { - await mainContent.sendMessage('0 1 2 3 4 5 6 7 8 9'); - await mainContent.waitForLastMessageEqualsHtml('0 1 2 3 4 5 6 7 8 9'); - }); - test('should render special characters', async () => { - await mainContent.sendMessage('® © ™ # *'); - await mainContent.waitForLastMessageEqualsHtml('® © ™ # *'); - }); + test('should render special characters', async () => { + await pageHomeChannel.content.doSendMessage('® © ™ # *'); + await expect(pageHomeChannel.content.lastUserMessage).toContainText('® © ™ # *'); }); }); }); diff --git a/apps/meteor/tests/e2e/08-resolutions.spec.ts b/apps/meteor/tests/e2e/08-resolutions.spec.ts deleted file mode 100644 index dfb354f84075..000000000000 --- a/apps/meteor/tests/e2e/08-resolutions.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { test, expect, Browser } from '@playwright/test'; - -import { Global, MainContent, SideNav, LoginPage } from './pageobjects'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; - -let loginPage: LoginPage; -let mainContent: MainContent; -let sideNav: SideNav; -let global: Global; - -async function initConfig(browser: Browser, options = { viewport: { width: 650, height: 800 } }): Promise { - const page = await browser.newPage(options); - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - mainContent = new MainContent(page); - global = new Global(page); - - await page.goto('/'); - await loginPage.doLogin(adminLogin); - return { loginPage, sideNav, mainContent }; -} - -test.describe('[Resolution]', function () { - test.describe('[Mobile Render]', async () => { - test.beforeAll(async ({ browser }) => { - await initConfig(browser); - }); - - test.afterAll(async ({ browser }) => { - await initConfig(browser, { viewport: { width: 1600, height: 1600 } }); - - await expect(sideNav.spotlightSearchIcon).toBeVisible(); - }); - - test('expect close the sidenav', async () => { - const position = await mainContent.mainContent.boundingBox(); - expect(position?.x).toEqual(0); - expect(await sideNav.isSideBarOpen()).toBeFalsy; - }); - - test.describe('moving elements:', async () => { - test.beforeEach(async () => { - if (!(await sideNav.isSideBarOpen())) { - await sideNav.burgerBtn.click({ force: true }); - } - }); - - test('expect open the sidenav', async () => { - const position = await mainContent.mainContent.boundingBox(); - expect(position?.x).toEqual(0); - expect(await sideNav.isSideBarOpen()).toBeTruthy; - }); - - test('expect not close sidebar on pressing the sidebar item menu', async () => { - await sideNav.firstSidebarItemMenu.click(); - - const position = await mainContent.mainContent.boundingBox(); - expect(position?.x).toEqual(0); - - expect(await sideNav.isSideBarOpen()).toBeTruthy; - - await sideNav.firstSidebarItemMenu.click(); - }); - - test('expect close the sidenav when open general channel', async () => { - await sideNav.doOpenChat('general'); - expect(await sideNav.isSideBarOpen()).toBeFalsy; - }); - - test.describe('Preferences', async () => { - test.beforeAll(async () => { - if (!(await sideNav.isSideBarOpen())) { - await sideNav.burgerBtn.click({ force: true }); - } - - await sideNav.sidebarUserMenu.click(); - await sideNav.account.click(); - }); - - test.afterEach(async () => { - await sideNav.returnToMenuInLowResolution.click(); - }); - - test.skip('expect close the sidenav when press the preferences link', async () => { - await sideNav.preferences.click(); - await expect(global.flexNav).toBeHidden(); - }); - - test.skip('expect close the sidenav when press the profile link', async () => { - await sideNav.profile.click(); - await expect(sideNav.flexNav).toBeHidden(); - }); - - test.skip('expect close the preferences nav', async () => { - await sideNav.preferencesClose.click(); - await expect(sideNav.flexNav).toBeHidden(); - }); - }); - }); - }); -}); diff --git a/apps/meteor/tests/e2e/09-channel.spec.ts b/apps/meteor/tests/e2e/09-channel.spec.ts index c5c575788344..3e481f0fb059 100644 --- a/apps/meteor/tests/e2e/09-channel.spec.ts +++ b/apps/meteor/tests/e2e/09-channel.spec.ts @@ -1,304 +1,251 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, Page } from '@playwright/test'; -import { Global, FlexTab, MainContent, SideNav, LoginPage } from './pageobjects'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; import { publicChannelCreated, setPublicChannelCreated } from './utils/mocks/checks'; +import { Auth, HomeChannel } from './page-objects'; const anyUser = 'rocket.cat'; const anyChannelName = `channel-test-${Date.now()}`; let hasUserAddedInChannel = false; -test.describe('[Channel]', () => { - let flexTab: FlexTab; - let loginPage: LoginPage; - let mainContent: MainContent; - let sideNav: SideNav; - let global: Global; +test.describe('Channel', () => { + let page: Page; + let pageAuth: Auth; + let pageHomeChannel: HomeChannel; test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); + page = await browser.newPage(); + pageAuth = new Auth(page); + pageHomeChannel = new HomeChannel(page); - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - mainContent = new MainContent(page); - flexTab = new FlexTab(page); - global = new Global(page); - - await page.goto('/'); - await loginPage.doLogin(adminLogin); + await pageAuth.doLogin(); if (!publicChannelCreated) { - await sideNav.doCreateChannel(anyChannelName, false); + await pageHomeChannel.sidenav.doCreateChannel(anyChannelName, false); setPublicChannelCreated(true); } - await sideNav.doOpenChat('general'); + await pageHomeChannel.sidenav.doOpenChat('general'); }); - test.describe('[Search]', () => { - test.describe('[SpotlightSearch]', async () => { + test.describe('Search', () => { + test.describe('SpotlightSearch', async () => { test('expect go to general', async () => { - await sideNav.doOpenChat('general'); - await expect(mainContent.channelTitle('general')).toContainText('general'); + await pageHomeChannel.sidenav.doOpenChat('general'); + await expect(pageHomeChannel.content.channelTitle('general')).toContainText('general'); }); test('expect go to the user created channel', async () => { - await sideNav.doOpenChat(anyChannelName); - await expect(mainContent.channelTitle(anyChannelName)).toContainText(anyChannelName); + await pageHomeChannel.sidenav.doOpenChat(anyChannelName); + await expect(pageHomeChannel.content.channelTitle(anyChannelName)).toContainText(anyChannelName); }); }); - test.describe('[SideNav Channel List]', () => { + test.describe('SideNav Channel List', () => { test.beforeAll(async () => { - await mainContent.messageInput.click(); + await pageHomeChannel.content.inputMain.click(); }); test('expect go to the general channel', async () => { - await sideNav.doOpenChat('general'); + await pageHomeChannel.sidenav.doOpenChat('general'); }); test('expect go to the user created channel', async () => { - await sideNav.doOpenChat(anyChannelName); + await pageHomeChannel.sidenav.doOpenChat(anyChannelName); }); }); }); - test.describe('[Usage]', () => { + test.describe('Usage', () => { test.beforeAll(async () => { - await sideNav.doOpenChat(anyChannelName); + await pageHomeChannel.sidenav.doOpenChat(anyChannelName); }); test.describe('Adding a user to the room:', async () => { test.beforeAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - await flexTab.btnTabMembers.click(); + await pageHomeChannel.doDismissToast(); + await pageHomeChannel.tabs.btnTabMembers.click(); }); test.afterAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - await flexTab.btnTabMembers.click(); + await pageHomeChannel.doDismissToast(); + await pageHomeChannel.tabs.btnTabMembers.click(); }); test('expect add people to the room', async () => { - await flexTab.addPeopleToChannel(anyUser); + await pageHomeChannel.tabs.doAddPeopleToChannel(anyUser); hasUserAddedInChannel = true; - await expect(global.getToastBarSuccess).toBeVisible(); + await expect(page.locator('.rcx-toastbar.rcx-toastbar--success')).toBeVisible(); }); }); - test.describe('[Channel settings]:', async () => { - test.describe('[Channel topic edit]', async () => { + test.describe('Channel settings]:', async () => { + test.describe('Channel topic edit', async () => { test.beforeAll(async () => { - await flexTab.btnTabInfo.click(); - await flexTab.editNameBtn.click(); + await pageHomeChannel.tabs.btnTabInfo.click(); + await pageHomeChannel.tabs.editNameBtn.click(); }); test.afterAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - if (await flexTab.mainSideBar.isVisible()) { - await flexTab.mainSideBarClose.click(); + await pageHomeChannel.doDismissToast(); + if (await pageHomeChannel.tabs.mainSideBar.isVisible()) { + await pageHomeChannel.tabs.mainSideBarClose.click(); } }); test('expect edit the topic input', async () => { - await flexTab.editTopicTextInput.fill('TOPIC EDITED'); + await pageHomeChannel.tabs.editTopicTextInput.fill('TOPIC EDITED'); }); test('expect save the topic', async () => { - await flexTab.editNameSave.click(); + await pageHomeChannel.tabs.editNameSave.click(); }); test('expect show the new topic', async () => { - await expect(flexTab.secondSetting('TOPIC EDITED')).toBeVisible(); + await expect(pageHomeChannel.tabs.secondSetting('TOPIC EDITED')).toBeVisible(); }); }); - test.describe('[Channel announcement edit]', async () => { + test.describe('Channel announcement edit', async () => { test.beforeAll(async () => { - await flexTab.btnTabInfo.click(); - await flexTab.editNameBtn.click(); + await pageHomeChannel.tabs.btnTabInfo.click(); + await pageHomeChannel.tabs.editNameBtn.click(); }); test.afterAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - if (await flexTab.mainSideBar.isVisible()) { - await flexTab.mainSideBarClose.click(); + await pageHomeChannel.doDismissToast(); + if (await pageHomeChannel.tabs.mainSideBar.isVisible()) { + await pageHomeChannel.tabs.mainSideBarClose.click(); } }); test('expect edit the announcement input', async () => { - await flexTab.editAnnouncementTextInput.type('ANNOUNCEMENT EDITED'); + await pageHomeChannel.tabs.editAnnouncementTextInput.type('ANNOUNCEMENT EDITED'); }); test('expect save the announcement', async () => { - await flexTab.editNameSave.click(); + await pageHomeChannel.tabs.editNameSave.click(); }); test('expect show the new announcement', async () => { - await expect(flexTab.thirdSetting).toHaveText('ANNOUNCEMENT EDITED'); + await expect(pageHomeChannel.tabs.thirdSetting).toHaveText('ANNOUNCEMENT EDITED'); }); }); - test.describe('[Channel description edit]', async () => { + test.describe('Channel description edit', async () => { test.beforeAll(async () => { - await flexTab.btnTabInfo.click(); - await flexTab.editNameBtn.click(); + await pageHomeChannel.tabs.btnTabInfo.click(); + await pageHomeChannel.tabs.editNameBtn.click(); }); test.afterAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - if (await flexTab.mainSideBar.isVisible()) { - await flexTab.mainSideBarClose.click(); + await pageHomeChannel.doDismissToast(); + if (await pageHomeChannel.tabs.mainSideBar.isVisible()) { + await pageHomeChannel.tabs.mainSideBarClose.click(); } }); test('expect edit the description input', async () => { - await flexTab.editDescriptionTextInput.type('DESCRIPTION EDITED'); + await pageHomeChannel.tabs.editDescriptionTextInput.type('DESCRIPTION EDITED'); }); test('expect save the description', async () => { - await flexTab.editNameSave.click(); + await pageHomeChannel.tabs.editNameSave.click(); }); test('expect show the new description', async () => { - await flexTab.mainSideBarBack.click(); - await expect(flexTab.fourthSetting).toHaveText('DESCRIPTION EDITED'); + await pageHomeChannel.tabs.mainSideBarBack.click(); + await expect(pageHomeChannel.tabs.fourthSetting).toHaveText('DESCRIPTION EDITED'); }); }); }); - test.describe('[Members tab usage]:', async () => { + test.describe('Members tab usage]:', async () => { test.describe('User muted', async () => { test.beforeAll(async () => { if (!hasUserAddedInChannel) { - await flexTab.btnTabMembers.click(); - await flexTab.addPeopleToChannel(anyUser); - await flexTab.btnTabMembers.click(); + await pageHomeChannel.tabs.btnTabMembers.click(); + await pageHomeChannel.tabs.doAddPeopleToChannel(anyUser); + await pageHomeChannel.tabs.btnTabMembers.click(); } - await flexTab.btnTabMembers.click(); + await pageHomeChannel.tabs.btnTabMembers.click(); }); test.afterAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - await flexTab.btnTabMembers.click(); + await pageHomeChannel.doDismissToast(); + await pageHomeChannel.tabs.btnTabMembers.click(); }); - test('expect mute rocket cat', async () => { - await flexTab.muteUser(anyUser); + test('expect mute "anyUser"', async () => { + await pageHomeChannel.tabs.doMuteUser(anyUser); }); }); - test.describe('[Owner added]', async () => { + test.describe('Owner added', async () => { test.beforeAll(async () => { if (!hasUserAddedInChannel) { - await flexTab.btnTabMembers.click(); - await flexTab.addPeopleToChannel(anyUser); - await flexTab.btnTabMembers.click(); + await pageHomeChannel.tabs.btnTabMembers.click(); + await pageHomeChannel.tabs.doAddPeopleToChannel(anyUser); + await pageHomeChannel.tabs.btnTabMembers.click(); } - await flexTab.btnTabMembers.click(); + await pageHomeChannel.tabs.btnTabMembers.click(); }); test.afterAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - await flexTab.btnTabMembers.click(); - }); - - test('expect set rocket cat as owner', async () => { - await flexTab.setUserOwner(anyUser); - }); - - test('expect dismiss the toast', async () => { - 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 pageHomeChannel.doDismissToast(); + await pageHomeChannel.tabs.btnTabMembers.click(); }); - test('expect show the target username in owner add message', async () => { - await expect(mainContent.lastMessageRoleAdded).toContainText(anyUser); + test('expect set "anyUser" as owner', async () => { + await pageHomeChannel.tabs.doSetUserOwner(anyUser); + await pageHomeChannel.doDismissToast(); + await expect(pageHomeChannel.content.lastMessageRoleAdded).toContainText(anyUser); }); }); - test.describe('[Moderator added]', async () => { + test.describe('Moderator added', async () => { test.beforeAll(async () => { if (!hasUserAddedInChannel) { - await flexTab.btnTabMembers.click(); - await flexTab.addPeopleToChannel(anyUser); - await flexTab.btnTabMembers.click(); + await pageHomeChannel.tabs.btnTabMembers.click(); + await pageHomeChannel.tabs.doAddPeopleToChannel(anyUser); + await pageHomeChannel.tabs.btnTabMembers.click(); } - await flexTab.btnTabMembers.click(); + await pageHomeChannel.tabs.btnTabMembers.click(); }); test.afterAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - await flexTab.btnTabMembers.click(); - }); - - test('expect set rocket cat as moderator', async () => { - await flexTab.setUserModerator(anyUser); + await pageHomeChannel.doDismissToast(); + await pageHomeChannel.tabs.btnTabMembers.click(); }); - test('expect be that the last message is a subscription role added', async () => { - await expect(mainContent.lastMessageRoleAdded).toBeVisible(); + test('expect set "anyUser" as moderator', async () => { + await pageHomeChannel.tabs.doSetUserModerator(anyUser); + await expect(pageHomeChannel.content.lastMessageRoleAdded).toContainText(anyUser); }); }); test.describe('Channel name edit', async () => { test.beforeAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } - await flexTab.btnTabInfo.click(); + await pageHomeChannel.doDismissToast(); + await pageHomeChannel.tabs.btnTabInfo.click(); }); test.afterAll(async () => { - if (await global.getToastBar.isVisible()) { - await global.dismissToastBar(); - } + await pageHomeChannel.doDismissToast(); - if (await flexTab.mainSideBar.isVisible()) { - await flexTab.btnTabInfo.click(); + if (await pageHomeChannel.tabs.mainSideBar.isVisible()) { + await pageHomeChannel.tabs.btnTabInfo.click(); } }); - test('expect show the old name', async () => { - await expect(flexTab.firstSetting).toHaveText(anyChannelName); - }); - - test('expect click the edit name', async () => { - await flexTab.editNameBtn.click(); - }); - - test('expect edit the name input', async () => { - await flexTab.editNameTextInput.fill(`NAME-EDITED-${anyChannelName}`); - }); - - test('expect save the name', async () => { - await flexTab.editNameSave.click(); + test('expect edit channel name', async () => { + await pageHomeChannel.tabs.editNameBtn.click(); + await pageHomeChannel.tabs.editNameTextInput.fill(`NAME-EDITED-${anyChannelName}`); + await pageHomeChannel.tabs.editNameSave.click(); }); test('expect to find and open with new name', async () => { - await sideNav.doOpenChat(`NAME-EDITED-${anyChannelName}`); + await pageHomeChannel.sidenav.doOpenChat(`NAME-EDITED-${anyChannelName}`); }); }); }); diff --git a/apps/meteor/tests/e2e/10-user-preferences.spec.ts b/apps/meteor/tests/e2e/10-user-preferences.spec.ts index f1a57caa07f4..abd04f34e685 100644 --- a/apps/meteor/tests/e2e/10-user-preferences.spec.ts +++ b/apps/meteor/tests/e2e/10-user-preferences.spec.ts @@ -1,56 +1,51 @@ -import { test, expect } from '@playwright/test'; -import faker from '@faker-js/faker'; +import { Page, test, expect } from '@playwright/test'; +import { faker } from '@faker-js/faker'; -import { PreferencesMainContent, MainContent, SideNav, LoginPage, FlexTab } from './pageobjects'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; +import { Auth, HomeChannel, AccountProfile } from './page-objects'; -test.describe('[User Preferences]', () => { - let flexTab: FlexTab; - let loginPage: LoginPage; - let mainContent: MainContent; - let sideNav: SideNav; - let preferencesMainContent: PreferencesMainContent; +test.describe('User preferences', () => { + let page: Page; + let pageAuth: Auth; + let pageHomeChannel: HomeChannel; + let pageAccountProfile: AccountProfile; + + const newName = faker.name.findName(); + const newUsername = faker.internet.userName(newName); test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - mainContent = new MainContent(page); - preferencesMainContent = new PreferencesMainContent(page); - flexTab = new FlexTab(page); + page = await browser.newPage(); + pageAuth = new Auth(page); + pageHomeChannel = new HomeChannel(page); + pageAccountProfile = new AccountProfile(page); + }); + + test.beforeAll(async () => { + await pageAuth.doLogin(); + }); + + test('expect update profile with new name and username', async () => { + await pageHomeChannel.sidenav.doOpenProfile(); + await pageAccountProfile.inputName.fill(newName); + await pageAccountProfile.inputUsername.fill(newUsername); + await pageAccountProfile.btnSubmit.click(); await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.sidebarUserMenu.click(); - await sideNav.account.click(); }); - test.describe('[Update UserInfo]', () => { - const newName = faker.name.findName(); - const newUserName = faker.internet.userName(newName); - - test('expect update profile with new name/username', async () => { - await sideNav.profile.click(); - await preferencesMainContent.inputName.fill(newName); - await preferencesMainContent.inputUsername.fill(newUserName); - await preferencesMainContent.submitBtn.click(); - }); - - test('expect show new username in the last message', async () => { - await sideNav.preferencesClose.click(); - await sideNav.doOpenChat('general'); - await mainContent.sendMessage('HI'); - - await expect(mainContent.lastUserMessage).toContainText(newUserName); - }); - - test('expect show new username in card and profile', async () => { - await mainContent.sendMessage('HI'); - await mainContent.btnLastUserMessage.click(); - await expect(mainContent.userCard).toBeVisible(); - - await mainContent.viewUserProfile.click(); - await expect(flexTab.userUsername).toHaveText(newUserName); - }); + test('expect show new username in the last message', async () => { + await pageHomeChannel.sidenav.doOpenChat('general'); + await pageHomeChannel.content.doSendMessage('any_message'); + + await expect(pageHomeChannel.content.lastUserMessageNotSequential).toContainText(newUsername); + }); + + test('expect show new username in card and profile', async () => { + await pageHomeChannel.sidenav.doOpenChat('general'); + await pageHomeChannel.content.doSendMessage('any_message'); + + await pageHomeChannel.content.lastUserMessageNotSequential.locator('figure').click(); + await pageHomeChannel.content.userCardLinkProfile.click(); + + await expect(pageHomeChannel.tabs.userInfoUsername).toHaveText(newUsername); }); }); diff --git a/apps/meteor/tests/e2e/11-admin.spec.ts b/apps/meteor/tests/e2e/11-admin.spec.ts index 9e933397e88e..f50284d29e89 100644 --- a/apps/meteor/tests/e2e/11-admin.spec.ts +++ b/apps/meteor/tests/e2e/11-admin.spec.ts @@ -1,460 +1,450 @@ import { test, expect, Page } from '@playwright/test'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; -import { FlexTab, Administration, LoginPage, SideNav } from './pageobjects'; -import { ROCKET_CAT_SELECTOR } from './utils/mocks/waitSelectorsMock'; +import { Auth, Administration } from './page-objects'; -test.describe('[Administration]', () => { +test.describe('Administration', () => { let page: Page; - let loginPage: LoginPage; - let sideNav: SideNav; - let admin: Administration; - let flexTab: FlexTab; - - const checkBoxesSelectors = ['Direct', 'Public', 'Private', 'Omnichannel', 'Discussions', 'Teams']; + let pageAuth: Auth; + let pageAdmin: Administration; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - flexTab = new FlexTab(page); - admin = new Administration(page); + pageAuth = new Auth(page); + pageAdmin = new Administration(page); + }); - await page.goto('/'); - await loginPage.doLogin(adminLogin); + test.beforeAll(async () => { + await pageAuth.doLogin(); + await page.goto('/admin'); }); - test.describe('[Admin View]', () => { - test.beforeAll(async () => { - await sideNav.sidebarUserMenu.click(); - await sideNav.admin.click(); - }); - test.describe('[Info]', () => { + test.describe('Admin View', () => { + 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 pageAdmin.infoLink.click(); + await expect(pageAdmin.infoDeployment).toBeVisible(); + await expect(pageAdmin.infoLicense).toBeVisible(); + await expect(pageAdmin.infoUsage).toBeVisible(); + await expect(pageAdmin.infoFederation).toBeVisible(); }); }); - test.describe('[Rooms]', () => { + test.describe('Rooms', () => { test.beforeAll(async () => { - await admin.roomsLink.click(); + await pageAdmin.roomsLink.click(); }); test.afterAll(async () => { - await admin.infoLink.click(); + await pageAdmin.infoLink.click(); }); - test.describe('[Render]', () => { + test.describe('Render', () => { test('expect rom page is rendered is rendered', async () => { - await admin.verifyCheckBoxRendered(checkBoxesSelectors); - await expect(admin.roomsSearchForm).toBeVisible(); + await pageAdmin.verifyCheckBoxRendered(['Direct', 'Public', 'Private', 'Omnichannel', 'Discussions', 'Teams']); + await expect(pageAdmin.roomsSearchForm).toBeVisible(); }); }); - test.describe('[Filter search input]', () => { + test.describe('Filter search input', () => { test.beforeAll(async () => { - await admin.roomsSearchForm.click(); + await pageAdmin.roomsSearchForm.click(); }); test.afterAll(async () => { - await admin.roomsSearchForm.click({ clickCount: 3 }); - await admin.keyboardPress('Backspace'); + await pageAdmin.roomsSearchForm.click({ clickCount: 3 }); + await page.keyboard.press('Backspace'); }); test('expect show the general channel', async () => { - await admin.roomsSearchForm.type('general'); - await expect(admin.roomsGeneralChannel).toBeVisible(); + await pageAdmin.roomsSearchForm.type('general'); + await expect(pageAdmin.roomsGeneralChannel).toBeVisible(); }); test('expect dont show rooms when room dont exist', async () => { - await admin.roomsSearchForm.type('any_room'); - await expect(admin.notFoundChannelOrUser).toBeVisible(); + await pageAdmin.roomsSearchForm.type('any_room'); + await expect(pageAdmin.notFoundChannelOrUser).toBeVisible(); }); }); - test.describe('[Filter checkbox]', () => { + test.describe('Filter checkbox', () => { test.beforeAll(async () => { - await admin.roomsSearchForm.click({ clickCount: 3 }); - await admin.keyboardPress('Backspace'); + await pageAdmin.roomsSearchForm.click({ clickCount: 3 }); + await page.keyboard.press('Backspace'); }); test('expect not show the general channel with direct', async () => { - await admin.adminCheckBox('Direct').click(); - await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel).not.toBeVisible(); - await admin.adminCheckBox('Direct').click(); + await pageAdmin.adminCheckBox('Direct').click(); + await pageAdmin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(pageAdmin.roomsGeneralChannel).not.toBeVisible(); + await pageAdmin.adminCheckBox('Direct').click(); }); test('expect show the general channel with public ', async () => { - await admin.adminCheckBox('Public').click(); - await admin.roomsGeneralChannel.waitFor({ state: 'visible' }); - await expect(admin.roomsGeneralChannel).toBeVisible(); - await admin.adminCheckBox('Public').click(); + await pageAdmin.adminCheckBox('Public').click(); + await pageAdmin.roomsGeneralChannel.waitFor({ state: 'visible' }); + await expect(pageAdmin.roomsGeneralChannel).toBeVisible(); + await pageAdmin.adminCheckBox('Public').click(); }); test('expect not show the general channel with private ', async () => { - await admin.adminCheckBox('Private').click(); - await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel).not.toBeVisible(); - await admin.adminCheckBox('Private').click(); + await pageAdmin.adminCheckBox('Private').click(); + await pageAdmin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(pageAdmin.roomsGeneralChannel).not.toBeVisible(); + await pageAdmin.adminCheckBox('Private').click(); }); test('expect not show the general channel with omnichannel', async () => { - await admin.adminCheckBox('Omnichannel').click(); - await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel).not.toBeVisible(); - await admin.adminCheckBox('Omnichannel').click(); + await pageAdmin.adminCheckBox('Omnichannel').click(); + await pageAdmin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(pageAdmin.roomsGeneralChannel).not.toBeVisible(); + await pageAdmin.adminCheckBox('Omnichannel').click(); }); test('expect not show the general channel with discussion', async () => { - await admin.adminCheckBox('Discussions').click(); - await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel).not.toBeVisible(); - await admin.adminCheckBox('Discussions').click(); + await pageAdmin.adminCheckBox('Discussions').click(); + await pageAdmin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(pageAdmin.roomsGeneralChannel).not.toBeVisible(); + await pageAdmin.adminCheckBox('Discussions').click(); }); test('expect not show the general channel with teams', async () => { - await admin.adminCheckBox('Teams').click(); - await admin.roomsGeneralChannel.waitFor({ state: 'detached' }); - await expect(admin.roomsGeneralChannel).not.toBeVisible(); - await admin.adminCheckBox('Teams').click(); + await pageAdmin.adminCheckBox('Teams').click(); + await pageAdmin.roomsGeneralChannel.waitFor({ state: 'detached' }); + await expect(pageAdmin.roomsGeneralChannel).not.toBeVisible(); + await pageAdmin.adminCheckBox('Teams').click(); }); }); - test.describe('[Users]', () => { + test.describe('Users', () => { test.beforeAll(async () => { - await admin.usersLink.click(); + await pageAdmin.usersLink.click(); }); test.afterAll(async () => { - await admin.infoLink.click(); + await pageAdmin.infoLink.click(); }); - test.describe('[Filter text]', async () => { + test.describe('Filter text', async () => { test.beforeEach(async () => { - await admin.usersFilter.click(); + await pageAdmin.usersFilter.click(); }); test.afterAll(async () => { - await admin.usersFilter.click(); - await admin.usersFilter.type(''); + await pageAdmin.usersFilter.click(); + await pageAdmin.usersFilter.type(''); }); test('expect should show rocket.cat', async () => { - await admin.usersFilter.type('rocket.cat'); - await page.waitForSelector(ROCKET_CAT_SELECTOR); + await pageAdmin.usersFilter.type('rocket.cat'); + await page.waitForSelector('//table//tbody//tr[1]//td//div//div//div//div[text()="Rocket.Cat"]'); }); + test('expect dont user when write wrong name', async () => { - await admin.usersFilter.type('any_user_wrong'); - await expect(admin.notFoundChannels).toBeVisible(); + await pageAdmin.usersFilter.type('any_user_wrong'); + await expect(pageAdmin.notFoundChannels).toBeVisible(); }); }); - test.describe('[Create user]', () => { + test.describe('Create user', () => { test.beforeAll(async () => { - await flexTab.usersAddUserTab.click(); + await pageAdmin.tabs.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(pageAdmin.tabs.usersAddUserName).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserUsername).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserEmail).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserVerifiedCheckbox).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserPassword).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserRoleList).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserRandomPassword).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserChangePasswordCheckbox).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserDefaultChannelCheckbox).toBeVisible(); + await expect(pageAdmin.tabs.usersAddUserWelcomeEmailCheckbox).toBeVisible(); + await expect(pageAdmin.tabs.usersButtonCancel).toBeVisible(); + await expect(pageAdmin.tabs.usersButtonSave).toBeVisible(); + + await pageAdmin.tabs.usersAddUserTabClose.click(); + + await expect(pageAdmin.tabs.addUserTable).not.toBeVisible(); }); }); }); }); - test.describe('[General Settings]', () => { + test.describe('General Settings', () => { test.beforeAll(async () => { - await admin.settingsLink.click(); - await admin.settingsSearch.type('general'); - await admin.generalSettingsButton.click(); + await pageAdmin.settingsLink.click(); + await pageAdmin.settingsSearch.type('general'); + await pageAdmin.generalSettingsButton.click(); }); - test.describe('[General]', () => { + 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.generalSiteUrlReset.click(); + await pageAdmin.generalSiteUrl.type('something'); + await expect(pageAdmin.generalSiteUrlReset).toBeVisible(); + await pageAdmin.generalSiteUrlReset.click(); }); test('expect change site name reset button is showed', async () => { - await admin.generalSiteName.type('something'); - await expect(admin.generalSiteNameReset).toBeVisible(); + await pageAdmin.generalSiteName.type('something'); + await expect(pageAdmin.generalSiteNameReset).toBeVisible(); }); test('expect show language field', async () => { - await expect(admin.generalLanguage).toBeVisible(); + await expect(pageAdmin.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 pageAdmin.generalSelfSignedCerts.click(); + await expect(pageAdmin.generalSelfSignedCertsReset).toBeVisible(); + await pageAdmin.generalSelfSignedCerts.click(); + await expect(pageAdmin.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 pageAdmin.generalFavoriteRoom.click(); + await expect(pageAdmin.generalFavoriteRoomReset).toBeVisible(); + await pageAdmin.generalFavoriteRoomReset.click(); + await expect(pageAdmin.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 pageAdmin.generalCdnPrefix.type('something'); + await expect(pageAdmin.generalCdnPrefixReset).toBeVisible(); + await pageAdmin.generalCdnPrefixReset.click(); + await expect(pageAdmin.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 pageAdmin.generalForceSSL.click(); + await expect(pageAdmin.generalForceSSLReset).toBeVisible(); + await pageAdmin.generalForceSSLReset.click(); + await expect(pageAdmin.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 pageAdmin.generalGoogleTagId.type('something'); + await expect(pageAdmin.generalGoogleTagIdReset).toBeVisible(); + await pageAdmin.generalGoogleTagIdReset.click(); + await expect(pageAdmin.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 pageAdmin.generalBugsnagKey.type('something'); + await expect(pageAdmin.generalBugsnagKeyReset).toBeVisible(); + await pageAdmin.generalBugsnagKeyReset.click(); + await expect(pageAdmin.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 pageAdmin.robotsFileContents.type('aa'); + await expect(pageAdmin.robotsFileContentsReset).toBeVisible(); + await pageAdmin.robotsFileContentsReset.click(); + await expect(pageAdmin.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 pageAdmin.defaultReferrerPolicy.click(); + await pageAdmin.defaultReferrerPolicyOptions.click(); + await expect(pageAdmin.defaultReferrerPolicyReset).toBeVisible(); + await pageAdmin.defaultReferrerPolicyReset.click(); + await expect(pageAdmin.defaultReferrerPolicyReset).not.toBeVisible(); }); }); - test.describe('[Iframe]', () => { + test.describe('Iframe', () => { test.beforeAll(async () => { - await admin.generalSectionIframeIntegration.click(); + await pageAdmin.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(pageAdmin.generalIframeSend).toBeVisible(); + await expect(pageAdmin.generalIframeSendTargetOrigin).toBeVisible(); + await expect(pageAdmin.generalIframeReceive).toBeVisible(); + await expect(pageAdmin.generalIframeReceiveOrigin).toBeVisible(); }); }); - test.describe('[Notifications]', () => { + test.describe('Notifications', () => { test.beforeAll(async () => { - await admin.generalSectionNotifications.click(); + await pageAdmin.generalSectionNotifications.click(); }); test('expect the max room members field', async () => { - await expect(admin.generalNotificationsMaxRoomMembers).toBeVisible(); + await expect(pageAdmin.generalNotificationsMaxRoomMembers).toBeVisible(); }); }); - test.describe('[Rest api]', async () => { + test.describe('Rest api', async () => { test.beforeAll(async () => { - await admin.generalSectionRestApi.click(); + await pageAdmin.generalSectionRestApi.click(); }); test('expect show the API user add limit field', async () => { - await expect(admin.generalRestApiUserLimit).toBeVisible(); + await expect(pageAdmin.generalRestApiUserLimit).toBeVisible(); }); }); - test.describe('[Reporting]', async () => { + test.describe('Reporting', async () => { test.beforeAll(async () => { - await admin.generalSectionReporting.click(); + await pageAdmin.generalSectionReporting.click(); }); test('expect show the report to rocket.chat toggle', async () => { - await expect(admin.generalReporting).toBeVisible(); + await expect(pageAdmin.generalReporting).toBeVisible(); }); }); - test.describe('[Stream cast]', async () => { + test.describe('Stream cast', async () => { test.beforeAll(async () => { - await admin.generalSectionStreamCast.click(); + await pageAdmin.generalSectionStreamCast.click(); }); test('expect show the stream cast address field', async () => { - await expect(admin.generalStreamCastAddress).toBeVisible(); + await expect(pageAdmin.generalStreamCastAddress).toBeVisible(); }); }); test.describe('UTF-8', () => { test.beforeAll(async () => { - await admin.generalSectionUTF8.click(); + await pageAdmin.generalSectionUTF8.click(); }); test('expect show the usernames utf8 regex field', async () => { - await expect(admin.generalUTF8UsernamesRegex).toBeVisible(); + await expect(pageAdmin.generalUTF8UsernamesRegex).toBeVisible(); }); test('expect show the channels utf8 regex field', async () => { - await expect(admin.generalUTF8ChannelsRegex).toBeVisible(); + await expect(pageAdmin.generalUTF8ChannelsRegex).toBeVisible(); }); test('expect show the utf8 names slug checkboxes', async () => { - await expect(admin.generalUTF8NamesSlug).toBeVisible(); + await expect(pageAdmin.generalUTF8NamesSlug).toBeVisible(); }); }); }); - test.describe('[Accounts]', () => { + test.describe('Accounts', () => { test.beforeAll(async () => { - await admin.groupSettingsPageBack.click(); - await admin.settingsSearch.type('accounts'); - await admin.accountSettingsButton.click(); + await pageAdmin.groupSettingsPageBack.click(); + await pageAdmin.settingsSearch.type('accounts'); + await pageAdmin.accountSettingsButton.click(); }); - test.describe('[Default user preferences]', () => { + test.describe('Default user preferences', () => { test.beforeAll(async () => { - await admin.accountsSectionDefaultUserPreferences.click(); + await pageAdmin.accountsSectionDefaultUserPreferences.click(); }); test('expect show the enable auto away field', async () => { - await expect(admin.accountsEnableAutoAway).toBeVisible(); + await expect(pageAdmin.accountsEnableAutoAway).toBeVisible(); }); test('the enable auto away field value should be true', async () => { - await admin.accountsEnableAutoAway.check(); + await pageAdmin.accountsEnableAutoAway.check(); }); test('expect show the idle timeout limit field', async () => { - await expect(admin.accountsIdleTimeLimit).toBeVisible(); - const inputValue = await admin.accountsIdleTimeLimit.inputValue(); + await expect(pageAdmin.accountsIdleTimeLimit).toBeVisible(); + const inputValue = await pageAdmin.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(pageAdmin.accountsDesktopNotifications).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsMobileNotifications).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsUnreadAlert).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsConvertAsciiEmoji.locator('input')).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsAutoImageLoad).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsAutoImageLoad).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsSaveMobileBandwidth).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsCollapseMediaByDefault).toBeVisible(); + await expect(pageAdmin.accountsCollapseMediaByDefault).not.toBeChecked(); }); test('expect show the hide usernames field', async () => { - await expect(admin.accountsHideUsernames).toBeVisible(); - await expect(admin.accountsHideUsernames).not.toBeChecked(); + await expect(pageAdmin.accountsHideUsernames).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsHideRoles).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsHideFlexTab).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsDisplayAvatars.locator('input')).toBeVisible(); + await expect(pageAdmin.accountsDisplayAvatars.locator('input')).toBeChecked(); }); test('expect show the enter key behavior field', async () => { - await expect(admin.accountsSendOnEnter).toBeVisible(); + await expect(pageAdmin.accountsSendOnEnter).toBeVisible(); - await expect(admin.accountsSendOnEnter.locator('.rcx-select__item')).toHaveText('Normal mode (send with Enter)'); + await expect(pageAdmin.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(pageAdmin.accountsMessageViewMode).toHaveText(''); }); test('expect show the offline email notification field and field value to be all', async () => { - await expect(admin.accountsEmailNotificationMode).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.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(pageAdmin.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(pageAdmin.accountsNewRoomNotification.locator('.rcx-select__item')).toHaveText('Default'); }); test('expect show the new message notification field', async () => { - await expect(admin.accountsNewMessageNotification).toBeVisible(); + await expect(pageAdmin.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(pageAdmin.accountsNewMessageNotification.locator('.rcx-select__item')).toHaveText('Default'); }); test('expect show the notification sound volume field', async () => { - await expect(admin.accountsNotificationsSoundVolume).toBeVisible(); + await expect(pageAdmin.accountsNotificationsSoundVolume).toBeVisible(); }); test('the notification sound volume field value should be 100', async () => { - await expect(admin.accountsNotificationsSoundVolume).toHaveValue('100'); + await expect(pageAdmin.accountsNotificationsSoundVolume).toHaveValue('100'); }); }); }); diff --git a/apps/meteor/tests/e2e/12-settings.spec.ts b/apps/meteor/tests/e2e/12-settings.spec.ts index bbcebb070d05..e8a3f5fa30aa 100644 --- a/apps/meteor/tests/e2e/12-settings.spec.ts +++ b/apps/meteor/tests/e2e/12-settings.spec.ts @@ -1,31 +1,26 @@ import { test, expect, Page } from '@playwright/test'; import { v4 as uuid } from 'uuid'; -import { BASE_API_URL } from './utils/mocks/urlMock'; +import { BASE_API_URL } from './utils/constants'; import { adminLogin, validUserInserted, registerUser } from './utils/mocks/userAndPasswordMock'; -import { LoginPage, MainContent, SideNav, Administration, PreferencesMainContent } from './pageobjects'; +import { Auth, HomeChannel, AccountProfile, Administration } from './page-objects'; const apiSessionHeaders = { 'X-Auth-Token': '', 'X-User-Id': '' }; -test.describe.skip('[Settings]', async () => { +test.describe.skip('Settings', async () => { let page: Page; - let loginPage: LoginPage; - let mainContent: MainContent; - let sideNav: SideNav; - let userPreferences: PreferencesMainContent; + let pageAuth: Auth; + let pageHomeChannel: HomeChannel; + let pageAccountProfile: AccountProfile; test.beforeAll(async ({ browser }) => { - const context = await browser.newContext(); - page = await context.newPage(); + page = await browser.newPage(); + pageAuth = new Auth(page); + pageHomeChannel = new HomeChannel(page); + pageAccountProfile = new AccountProfile(page); - loginPage = new LoginPage(page); - mainContent = new MainContent(page); - sideNav = new SideNav(page); - userPreferences = new PreferencesMainContent(page); - - await page.goto('/'); - await loginPage.doLogin(validUserInserted); - await sideNav.general.click(); + await pageAuth.doLogin(validUserInserted); + await pageHomeChannel.sidenav.doOpenChat('general'); }); test.beforeAll(async ({ request }) => { @@ -54,9 +49,9 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(edit) not be visible', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="edit-message"]')).toBeFalsy(); }); @@ -73,9 +68,9 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(edit) be visible', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="edit-message"]')).toBeTruthy(); }); @@ -94,9 +89,9 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(delete) not be visible', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="delete-message"]')).toBeFalsy(); }); @@ -113,9 +108,9 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(delete) be visible', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="delete-message"]')).toBeTruthy(); }); @@ -134,9 +129,9 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(upload audio) not be visible', async () => { - await mainContent.doReload(); + await pageHomeChannel.content.doReload(); - expect(await mainContent.recordBtn.isVisible()).toBeFalsy(); + expect(await pageHomeChannel.content.btnAudioRecod.isVisible()).toBeFalsy(); }); test('(API) expect enable audio files', async ({ request }) => { @@ -151,9 +146,9 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(upload audio) be visible', async () => { - await mainContent.doReload(); + await pageHomeChannel.content.doReload(); - expect(await mainContent.recordBtn.isVisible()).toBeTruthy(); + expect(await pageHomeChannel.content.btnAudioRecod.isVisible()).toBeTruthy(); }); }); @@ -170,8 +165,8 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(upload video) not be visible', async () => { - await mainContent.doReload(); - await mainContent.openMoreActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.openMoreActionMenu(); expect(await page.isVisible('.rc-popover__content [data-id="video-message"]')).toBeFalsy(); }); @@ -188,8 +183,8 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(upload video) be visible', async () => { - await mainContent.doReload(); - await mainContent.openMoreActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.openMoreActionMenu(); expect(await page.isVisible('.rc-popover__content [data-id="video-message"]')).toBeTruthy(); }); @@ -221,10 +216,10 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect badword be censored', async () => { - await mainContent.doReload(); + await pageHomeChannel.content.doReload(); - await mainContent.sendMessage(unauthorizedWord); - await mainContent.waitForLastMessageEqualsText('*'.repeat(unauthorizedWord.length)); + await pageHomeChannel.content.doSendMessage(unauthorizedWord); + await expect(pageHomeChannel.content.lastMessage).toContainText('*'.repeat(unauthorizedWord.length)); }); test('(API) expect disable bad words filter', async ({ request }) => { @@ -239,10 +234,10 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect badword not be censored', async () => { - await mainContent.doReload(); + await pageHomeChannel.content.doReload(); - await mainContent.sendMessage(unauthorizedWord); - await mainContent.waitForLastMessageEqualsText(unauthorizedWord); + await pageHomeChannel.content.doSendMessage(unauthorizedWord); + await expect(pageHomeChannel.content.lastMessage).toContainText(unauthorizedWord); }); }); @@ -259,9 +254,9 @@ test.describe.skip('[Settings]', async () => { }); test.skip('(UI) expect option(star message) not be visible', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="star-message"]')).toBeFalsy(); }); @@ -278,9 +273,9 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(star message) be visible', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="star-message"]')).toBeTruthy(); }); @@ -299,8 +294,8 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(upload file) not be visible', async () => { - await mainContent.doReload(); - await mainContent.openMoreActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.openMoreActionMenu(); expect(await page.isVisible('[data-qa-id="file-upload"]')).toBeFalsy(); }); @@ -317,8 +312,8 @@ test.describe.skip('[Settings]', async () => { }); test('(UI) expect option(upload file) be visible', async () => { - await mainContent.doReload(); - await mainContent.openMoreActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.openMoreActionMenu(); expect(await page.isVisible('[data-qa-id="file-upload"]')).toBeTruthy(); }); @@ -337,13 +332,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 pageHomeChannel.sidenav.btnAvatar.click(); + await pageHomeChannel.sidenav.linkAccount.click(); - expect(userPreferences.avatarFileInput.isDisabled()).toBeTruthy(); - expect(userPreferences.emailTextInput.isDisabled()).toBeTruthy(); - expect(userPreferences.inputName.isDisabled()).toBeTruthy(); - expect(userPreferences.inputUsername.isDisabled()).toBeTruthy(); + expect(pageAccountProfile.avatarFileInput.isDisabled()).toBeTruthy(); + expect(pageAccountProfile.emailTextInput.isDisabled()).toBeTruthy(); + expect(pageAccountProfile.inputName.isDisabled()).toBeTruthy(); + expect(pageAccountProfile.inputUsername.isDisabled()).toBeTruthy(); }); test('(API) expect enable profile change', async ({ request }) => { @@ -371,10 +366,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 pageHomeChannel.sidenav.btnAvatar.click(); + await pageHomeChannel.sidenav.linkAccount.click(); - expect(userPreferences.avatarFileInput.isDisabled()).toBeTruthy(); + expect(pageAccountProfile.avatarFileInput.isDisabled()).toBeTruthy(); }); test('(API) expect enable avatar change', async ({ request }) => { @@ -390,25 +385,21 @@ test.describe.skip('[Settings]', async () => { }); }); -test.describe.skip('[Settings (admin)]', async () => { +test.describe.skip('Settings (admin)', async () => { let page: Page; - let loginPage: LoginPage; - let mainContent: MainContent; - let sideNav: SideNav; - let admin: Administration; + let pageAuth: Auth; + let pageHomeChannel: HomeChannel; + let pageAdmin: Administration; test.beforeAll(async ({ browser }) => { - const context = await browser.newContext(); - page = await context.newPage(); - - loginPage = new LoginPage(page); - mainContent = new MainContent(page); - sideNav = new SideNav(page); - admin = new Administration(page); + page = await browser.newPage(); + pageAuth = new Auth(page); + pageHomeChannel = new HomeChannel(page); + pageAdmin = new Administration(page); await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.general.click(); + await pageAuth.doLogin(); + await pageHomeChannel.sidenav.doOpenChat('general'); }); test.beforeAll(async ({ request }) => { @@ -432,9 +423,9 @@ test.describe.skip('[Settings (admin)]', async () => { }); test('(UI) expect option(pin message) not be visible', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="pin-message"]')).toBeFalsy(); }); @@ -451,9 +442,9 @@ test.describe.skip('[Settings (admin)]', async () => { }); test('(UI) expect option(pin message) be visible', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="pin-message"]')).toBeTruthy(); }); @@ -474,22 +465,22 @@ test.describe.skip('[Settings (admin)]', async () => { test.describe('(UI) expect activate/deactivate flow as admin', () => { test('expect open /users as admin', async () => { await page.goto('/admin'); - await admin.usersLink.click(); + await pageAdmin.usersLink.click(); }); test('expect find registered user', async () => { - await admin.usersFilter.type(registerUser.email, { delay: 200 }); - await admin.userInTable(registerUser.email).click(); + await pageAdmin.usersFilter.type(registerUser.email, { delay: 200 }); + await pageAdmin.userInTable(registerUser.email).click(); }); test('expect activate registered user', async () => { - await admin.userInfoActions.locator('button:nth-child(3)').click(); - await admin.page.locator('[value="changeActiveStatus"]').click(); + await pageAdmin.userInfoActions.locator('button:nth-child(3)').click(); + await page.locator('value="changeActiveStatus"]').click(); }); test('expect deactivate registered user', async () => { - await admin.userInfoActions.locator('button:nth-child(3)').click(); - await admin.page.locator('[value="changeActiveStatus"]').click(); + await pageAdmin.userInfoActions.locator('button:nth-child(3)').click(); + await 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 96d9ad1ac339..57db1fa1f841 100644 --- a/apps/meteor/tests/e2e/13-permissions.spec.ts +++ b/apps/meteor/tests/e2e/13-permissions.spec.ts @@ -1,96 +1,93 @@ import { Page, test, expect } from '@playwright/test'; import { v4 as uuid } from 'uuid'; +import faker from '@faker-js/faker'; -import { LoginPage, FlexTab, Administration, MainContent, SideNav } from './pageobjects'; -import { adminLogin, createRegisterUser } from './utils/mocks/userAndPasswordMock'; -import { BACKSPACE } from './utils/mocks/keyboardKeyMock'; +import { Auth, Administration, HomeChannel } from './page-objects'; -test.describe('[Permissions]', () => { +test.describe('Permissions', () => { let page: Page; + let pageAuth: Auth; + let pageAdmin: Administration; + let pageHomeChannel: HomeChannel; - let loginPage: LoginPage; - let admin: Administration; - let flexTab: FlexTab; - let sideNav: SideNav; - let mainContent: MainContent; - - const userToBeCreated = createRegisterUser(); + const anyUser = { + email: faker.internet.email(), + password: 'any_password', + name: faker.name.findName(), + username: faker.internet.userName(), + }; test.beforeAll(async ({ browser }) => { - const context = await browser.newContext(); - page = await context.newPage(); - - loginPage = new LoginPage(page); - admin = new Administration(page); - flexTab = new FlexTab(page); - sideNav = new SideNav(page); - mainContent = new MainContent(page); - - await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.sidebarUserMenu.click(); - await sideNav.admin.click(); - await sideNav.users.click(); + page = await browser.newPage(); + pageAuth = new Auth(page); + pageAdmin = new Administration(page); + pageHomeChannel = new HomeChannel(page); + + await pageAuth.doLogin(); + await pageHomeChannel.sidenav.btnAvatar.click(); + await pageHomeChannel.sidenav.linkAdmin.click(); + await pageAdmin.sidenav.linkUsers.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.doAddRole('user'); - await flexTab.usersButtonSave.click(); + await pageAdmin.tabs.usersAddUserTab.click(); + await pageAdmin.tabs.usersAddUserName.type(anyUser.name); + await pageAdmin.tabs.usersAddUserUsername.type(anyUser.username); + await pageAdmin.tabs.usersAddUserEmail.type(anyUser.email); + await pageAdmin.tabs.usersAddUserVerifiedCheckbox.click(); + await pageAdmin.tabs.usersAddUserPassword.type(anyUser.password); + await pageAdmin.tabs.doAddRole('user'); + await pageAdmin.tabs.usersButtonSave.click(); }); test('expect user be show on list', async () => { - await admin.usersFilter.type(userToBeCreated.email, { delay: 200 }); - await expect(admin.userInTable(userToBeCreated.email)).toBeVisible(); + await pageAdmin.usersFilter.type(anyUser.email, { delay: 200 }); + await expect(pageAdmin.userInTable(anyUser.email)).toBeVisible(); }); - test.describe('disable "userToBeCreated" permissions', () => { + test.describe('disable "anyUser" permissions', () => { test('expect open permissions table', async () => { - await admin.permissionsLink.click(); + await pageAdmin.permissionsLink.click(); }); test('expect remove "mention all" permission from user', async () => { - await admin.inputPermissionsSearch.type('all'); + await pageAdmin.inputPermissionsSearch.type('all'); - if (await admin.getCheckboxPermission('Mention All').locator('input').isChecked()) { - await admin.getCheckboxPermission('Mention All').click(); + if (await pageAdmin.getCheckboxPermission('Mention All').locator('input').isChecked()) { + await pageAdmin.getCheckboxPermission('Mention All').click(); } }); test('expect remove "delete message" permission from user', async () => { - await admin.inputPermissionsSearch.click({ clickCount: 3 }); - await page.keyboard.press(BACKSPACE); - await admin.inputPermissionsSearch.type('delete'); + await pageAdmin.inputPermissionsSearch.click({ clickCount: 3 }); + await page.keyboard.press('Backspace'); + await pageAdmin.inputPermissionsSearch.type('delete'); - if (await admin.getCheckboxPermission('Delete Own Message').locator('input').isChecked()) { - await admin.getCheckboxPermission('Delete Own Message').click(); + if (await pageAdmin.getCheckboxPermission('Delete Own Message').locator('input').isChecked()) { + await pageAdmin.getCheckboxPermission('Delete Own Message').click(); } }); }); - test.describe('assert "userToBeCreated" permissions', () => { + test.describe('assert "anyUser" permissions', () => { test.beforeAll(async () => { - await sideNav.doLogout(); + await pageHomeChannel.sidenav.doLogout(); + await page.goto('/'); - await loginPage.doLogin(userToBeCreated); - await sideNav.general.click(); + await pageAuth.doLogin(anyUser); + await pageHomeChannel.sidenav.doOpenChat('general'); }); test('expect not be abble to "mention all"', async () => { - await mainContent.sendMessage('@all any_message'); + await pageHomeChannel.content.doSendMessage('@all any_message'); - await expect(mainContent.lastMessageForMessageTest).toContainText('not allowed'); + await expect(pageHomeChannel.content.lastMessageForMessageTest).toContainText('not allowed'); }); test('expect not be able to "delete own message"', async () => { - await mainContent.doReload(); - await mainContent.sendMessage(`any_message_${uuid()}`); - await mainContent.openMessageActionMenu(); + await pageHomeChannel.content.doReload(); + await pageHomeChannel.content.doSendMessage(`any_message_${uuid()}`); + await pageHomeChannel.content.doOpenMessageActionMenu(); expect(await page.isVisible('[data-qa-id="delete-message"]')).toBeFalsy(); }); diff --git a/apps/meteor/tests/e2e/14-setting-permissions.spec.ts b/apps/meteor/tests/e2e/14-setting-permissions.spec.ts index 00f0142397a0..b888aa76985e 100644 --- a/apps/meteor/tests/e2e/14-setting-permissions.spec.ts +++ b/apps/meteor/tests/e2e/14-setting-permissions.spec.ts @@ -1,116 +1,115 @@ import { test, expect, Page } from '@playwright/test'; import faker from '@faker-js/faker'; -import { adminLogin, validUserInserted } from './utils/mocks/userAndPasswordMock'; -import { SideNav, Administration, LoginPage } from './pageobjects'; +import { validUserInserted } from './utils/mocks/userAndPasswordMock'; +import { Auth, Administration, HomeChannel } from './page-objects'; -test.describe('[Rocket.Chat Settings based permissions]', () => { +test.describe('Settings Permissions', () => { let page: Page; - let admin: Administration; - let sideNav: SideNav; - let loginPage: LoginPage; + let pageAuth: Auth; + let pageAdmin: Administration; + let pageHomeChannel: HomeChannel; const newHomeTitle = faker.animal.type(); test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - sideNav = new SideNav(page); - admin = new Administration(page); - loginPage = new LoginPage(page); + pageAuth = new Auth(page); + pageAdmin = new Administration(page); + pageHomeChannel = new HomeChannel(page); }); - test.describe('[Give User Permissions]', async () => { + test.describe('Give User Permissions', async () => { test.beforeAll(async () => { - await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.sidebarUserMenu.click(); - await sideNav.admin.click(); - await admin.permissionsLink.click(); + await pageAuth.doLogin(); + await pageHomeChannel.sidenav.btnAvatar.click(); + await pageHomeChannel.sidenav.linkAdmin.click(); + await pageAdmin.permissionsLink.click(); }); test.afterAll(async () => { - await sideNav.doLogout(); + await pageHomeChannel.sidenav.doLogout(); }); test('Set permission for user to manage settings', async () => { - 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'); + await pageAdmin.rolesSettingsFindInput.type('settings'); + await page.locator('table tbody tr:first-child td:nth-child(1) >> text="Change some settings"').waitFor(); + const isOptionChecked = await page.isChecked('table tbody tr:first-child td:nth-child(6) label input'); if (!isOptionChecked) { - await admin.page.click('table tbody tr:first-child td:nth-child(6) label'); + await 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.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'); + await pageAdmin.rolesSettingsTab.click(); + await pageAdmin.rolesSettingsFindInput.fill('Layout'); + await page.locator('table tbody tr:first-child td:nth-child(1) >> text="Layout"').waitFor(); + const isOptionChecked = await page.isChecked('table tbody tr:first-child td:nth-child(6) label input'); + const changeHomeTitleSelected = await page.isChecked('table tbody tr:nth-child(3) td:nth-child(6) label input'); if (!isOptionChecked && !changeHomeTitleSelected) { - 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'); + await page.click('table tbody tr:first-child td:nth-child(6) label'); + await page.click('table tbody tr:nth-child(3) td:nth-child(6) label'); } }); }); - test.describe('[Test new user setting permissions]', async () => { + test.describe('Test new user setting permissions', async () => { test.beforeAll(async () => { await page.goto('/'); - await loginPage.doLogin(validUserInserted); + await pageAuth.doLogin(validUserInserted); - await sideNav.sidebarUserMenu.click(); - await sideNav.admin.click(); - await admin.settingsLink.click(); - await admin.layoutSettingsButton.click(); + await pageHomeChannel.sidenav.btnAvatar.click(); + await pageHomeChannel.sidenav.linkAdmin.click(); + await pageAdmin.settingsLink.click(); + await pageAdmin.layoutSettingsButton.click(); }); test.afterAll(async () => { - await sideNav.doLogout(); + await pageHomeChannel.sidenav.doLogout(); }); test('expect new permissions is enabled for user', async () => { - await admin.homeTitleInput.fill(newHomeTitle); - await admin.buttonSave.click(); + await pageAdmin.homeTitleInput.fill(newHomeTitle); + await pageAdmin.buttonSave.click(); }); }); - test.describe('[Verify settings change and cleanup]', async () => { + test.describe('Verify settings change and cleanup', async () => { test.beforeAll(async () => { await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.sidebarUserMenu.click(); - await sideNav.admin.click(); - await admin.settingsLink.click(); - await admin.settingsSearch.type('Layout'); - await admin.layoutSettingsButton.click(); + await pageAuth.doLogin(); + await pageHomeChannel.sidenav.btnAvatar.click(); + await pageHomeChannel.sidenav.linkAdmin.click(); + await pageAdmin.settingsLink.click(); + await pageAdmin.settingsSearch.type('Layout'); + await pageAdmin.layoutSettingsButton.click(); }); test.afterAll(async () => { - await sideNav.doLogout(); + await pageHomeChannel.sidenav.doLogout(); }); test('New settings value visible for admin as well', async () => { - 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(); + await page.locator('[data-qa-section="Content"]').click(); + await pageAdmin.homeTitleInput.waitFor(); + const text = await pageAdmin.homeTitleInput.inputValue(); + await pageAdmin.generalHomeTitleReset.click(); + await pageAdmin.buttonSave.click(); expect(text).toEqual(newHomeTitle); }); test('Clear all user permissions', async () => { - 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.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'); + await pageAdmin.permissionsLink.click(); + await pageAdmin.rolesSettingsFindInput.type('settings'); + await page.locator('table tbody tr:first-child td:nth-child(1) >> text="Change some settings"').waitFor(); + await page.click('table tbody tr:first-child td:nth-child(6) label'); + + await pageAdmin.rolesSettingsTab.click(); + await pageAdmin.rolesSettingsFindInput.fill('Layout'); + await page.locator('table tbody tr:first-child td:nth-child(1) >> text="Layout"').waitFor(); + await page.click('table tbody tr td:nth-child(6) label'); + await 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 9700d0dc1b97..97fa98a3430f 100644 --- a/apps/meteor/tests/e2e/15-message-popup.spec.ts +++ b/apps/meteor/tests/e2e/15-message-popup.spec.ts @@ -1,52 +1,42 @@ import { Page, test, expect } from '@playwright/test'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; -import { userMock } from './utils/mocks/userMock'; -import { LoginPage, MainContent, SideNav } from './pageobjects'; +import { Auth, HomeChannel } from './page-objects'; -test.describe('[Message Popup]', () => { +test.describe('Message Popup', () => { let page: Page; - let loginPage: LoginPage; - let mainContent: MainContent; - let sideNav: SideNav; + let pageAuth: Auth; + let pageHomeChannel: HomeChannel; test.beforeAll(async ({ browser }) => { - const context = await browser.newContext(); - page = await context.newPage(); - - loginPage = new LoginPage(page); - mainContent = new MainContent(page); - sideNav = new SideNav(page); + page = await browser.newPage(); + pageAuth = new Auth(page); + pageHomeChannel = new HomeChannel(page); + }); - await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.doOpenChat('public channel'); + test.beforeAll(async () => { + await pageAuth.doLogin(); + await pageHomeChannel.sidenav.doOpenChat('public channel'); }); test.describe('User mentions', () => { test('expect show message popup', async () => { - await mainContent.setTextToInput('@'); - expect(await mainContent.messagePopUp.isVisible()).toBeTruthy(); + await pageHomeChannel.content.setTextToInput('@'); + expect(await pageHomeChannel.content.messagePopUp.isVisible()).toBeTruthy(); }); test('expect popup title to be people', async () => { - await mainContent.setTextToInput('@'); - 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(); + await pageHomeChannel.content.setTextToInput('@'); + await expect(pageHomeChannel.content.messagePopUpTitle.locator('text=People')).toBeVisible(); }); test('expect show "all" option', async () => { - await mainContent.setTextToInput('@'); - expect(await mainContent.messagePopUpItems.locator('text=all').isVisible()).toBeTruthy(); + await pageHomeChannel.content.setTextToInput('@'); + await expect(pageHomeChannel.content.messagePopUpItems.locator('text=all')).toBeVisible(); }); test('expect show "here" option', async () => { - await mainContent.setTextToInput('@'); - expect(await mainContent.messagePopUpItems.locator('text=here').isVisible()).toBeTruthy(); + await pageHomeChannel.content.setTextToInput('@'); + await expect(pageHomeChannel.content.messagePopUpItems.locator('text=here')).toBeVisible(); }); }); }); diff --git a/apps/meteor/tests/e2e/16-discussion.spec.ts b/apps/meteor/tests/e2e/16-discussion.spec.ts index f95ae20ea9a3..9efeac48ab9f 100644 --- a/apps/meteor/tests/e2e/16-discussion.spec.ts +++ b/apps/meteor/tests/e2e/16-discussion.spec.ts @@ -2,49 +2,45 @@ import { test, Page } from '@playwright/test'; import { faker } from '@faker-js/faker'; import { v4 as uuid } from 'uuid'; -import { MainContent, Discussion, LoginPage, SideNav } from './pageobjects'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; +import { Auth, HomeDiscussion } from './page-objects'; test.describe('[Discussion]', () => { let page: Page; - let loginPage: LoginPage; - let discussion: Discussion; - let sideNav: SideNav; - let mainContent: MainContent; - - let message: string; + let pageAuth: Auth; + let pageHomeDiscussion: HomeDiscussion; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - loginPage = new LoginPage(page); - discussion = new Discussion(page); - sideNav = new SideNav(page); - mainContent = new MainContent(page); + pageAuth = new Auth(page); + pageHomeDiscussion = new HomeDiscussion(page); + }); - await page.goto('/'); - await loginPage.doLogin(adminLogin); + test.beforeAll(async () => { + await pageAuth.doLogin(); }); test.describe('[Create discussion from screen]', () => { test('expect discussion is created', async () => { - const discussionName = faker.animal.type() + Date.now(); - message = faker.animal.type(); - await sideNav.btnSidebarCreate.click(); - await discussion.doCreateDiscussion('public channel', discussionName, message); + const anyDiscussionName = faker.animal.type() + Date.now(); + const anyMessage = faker.animal.type(); + + await pageHomeDiscussion.sidenav.btnCreate.click(); + await pageHomeDiscussion.doCreateDiscussion('public channel', anyDiscussionName, anyMessage); }); }); test.describe.skip('[Create discussion from context menu]', () => { + const anyMessage = faker.animal.type() + uuid(); + test.beforeAll(async () => { - message = faker.animal.type() + uuid(); - await sideNav.doOpenChat('public channel'); - await mainContent.sendMessage(message); + await pageHomeDiscussion.sidenav.doOpenChat('public channel'); + await pageHomeDiscussion.content.doSendMessage(anyMessage); }); test('expect show a dialog for starting a discussion', async () => { - await mainContent.page.waitForLoadState('domcontentloaded', { timeout: 3000 }); - await mainContent.openMessageActionMenu(); - await discussion.createDiscussionInContext(message); + await page.waitForLoadState('domcontentloaded', { timeout: 3000 }); + await pageHomeDiscussion.content.doOpenMessageActionMenu(); + await pageHomeDiscussion.doCreateDiscussionInContext(anyMessage); }); }); }); diff --git a/apps/meteor/tests/e2e/omnichannel-agents.spec.ts b/apps/meteor/tests/e2e/omnichannel-agents.spec.ts index c844d5a8b3d5..1ee683bd5ed2 100644 --- a/apps/meteor/tests/e2e/omnichannel-agents.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel-agents.spec.ts @@ -1,116 +1,101 @@ import { test, expect, Page } from '@playwright/test'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; -import { LoginPage, SideNav, Agents, Global } from './pageobjects'; +import { Auth, OmnichannelAgents } from './page-objects'; -test.describe('[Agents]', () => { - let loginPage: LoginPage; +test.describe('Agents', () => { let page: Page; - let sideNav: SideNav; - let agents: Agents; - let global: Global; + let pageAuth: Auth; + let pageOmnichannelAgents: OmnichannelAgents; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - agents = new Agents(page); - global = new Global(page); - - await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.sidebarUserMenu.click(); - await sideNav.omnichannel.click(); - await agents.agentsLink.click(); - await agents.doAddAgent(); + pageAuth = new Auth(page); + pageOmnichannelAgents = new OmnichannelAgents(page); + }); + + test.beforeAll(async () => { + await pageAuth.doLogin(); + await page.goto('/omnichannel'); }); test('expect admin/manager is able to add an agent', async () => { - await expect(agents.agentAdded).toBeVisible(); - await expect(agents.agentAdded).toHaveText('Rocket.Cat'); + await pageOmnichannelAgents.agentsLink.click(); + await pageOmnichannelAgents.doAddAgent(); + await expect(pageOmnichannelAgents.agentAdded).toBeVisible(); + await expect(pageOmnichannelAgents.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 pageOmnichannelAgents.agentAdded.click(); + await expect(pageOmnichannelAgents.userInfoTab).toBeVisible(); + await expect(pageOmnichannelAgents.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 pageOmnichannelAgents.btnClose.click(); + await expect(pageOmnichannelAgents.userInfoTab).not.toBeVisible(); + await expect(pageOmnichannelAgents.agentInfo).not.toBeVisible(); + await pageOmnichannelAgents.agentAdded.click(); }); - test.describe('[Render]', () => { + test.describe('Render', () => { test('expect show profile image', async () => { - await expect(agents.userAvatar).toBeVisible(); + await expect(pageOmnichannelAgents.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(pageOmnichannelAgents.btnClose).toBeVisible(); + await expect(pageOmnichannelAgents.btnEdit).toBeVisible(); + await expect(pageOmnichannelAgents.btnRemove).toBeVisible(); }); test('expect show livechat status', async () => { - await expect(agents.agentInfoUserInfoLabel).toBeVisible(); + await expect(pageOmnichannelAgents.agentInfoUserInfoLabel).toBeVisible(); }); }); - test.describe('[Edit button]', async () => { - test.describe('[Render]', async () => { - test.beforeAll(async () => { - await agents.btnEdit.click(); - }); - - test('expect show fields', async () => { - await agents.getListOfExpectedInputs(); - }); - }); - - test.describe('[Action]', async () => { + test.describe('Edit button', async () => { + test.describe('Action', async () => { test('expect change user status', async () => { - await agents.doChangeUserStatus('not-available'); - await expect(agents.agentListStatus).toHaveText('Not Available'); + await pageOmnichannelAgents.doChangeUserStatus('not-available'); + await expect(pageOmnichannelAgents.agentListStatus).toHaveText('Not Available'); }); - test.describe('[Modal Actions]', async () => { + test.describe('Modal Actions', async () => { test.beforeEach(async () => { - await agents.doRemoveAgent(); + await pageOmnichannelAgents.doRemoveAgent(); }); test('expect modal is not visible after cancel delete agent', async () => { - await global.btnModalCancel.click(); - await expect(global.modal).not.toBeVisible(); + await pageOmnichannelAgents.btnModalCancel.click(); + await expect(pageOmnichannelAgents.modal).not.toBeVisible(); }); test('expect agent is removed from user info tab', async () => { - await global.btnModalRemove.click(); - await expect(global.modal).not.toBeVisible(); - await expect(agents.agentAdded).not.toBeVisible(); + await pageOmnichannelAgents.btnModalRemove.click(); + await expect(pageOmnichannelAgents.modal).not.toBeVisible(); + await expect(pageOmnichannelAgents.agentAdded).not.toBeVisible(); }); }); - test.describe('[Remove from table]', async () => { + test.describe('Remove from table', async () => { test.beforeAll(async () => { - await agents.doAddAgent(); + await pageOmnichannelAgents.doAddAgent(); }); test.beforeEach(async () => { - await agents.btnTableRemove.click(); + await pageOmnichannelAgents.btnTableRemove.click(); }); test('expect modal is not visible after cancel delete agent', async () => { - await global.btnModalCancel.click(); - await expect(global.modal).not.toBeVisible(); + await pageOmnichannelAgents.btnModalCancel.click(); + await expect(pageOmnichannelAgents.modal).not.toBeVisible(); }); test('expect agent is removed from agents table', async () => { - await global.btnModalRemove.click(); - await expect(global.modal).not.toBeVisible(); - await expect(agents.agentAdded).not.toBeVisible(); + await pageOmnichannelAgents.btnModalRemove.click(); + await expect(pageOmnichannelAgents.modal).not.toBeVisible(); + await expect(pageOmnichannelAgents.agentAdded).not.toBeVisible(); }); }); }); diff --git a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts index f38afebcff7d..f275cca885c7 100644 --- a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts @@ -1,77 +1,61 @@ import { test, Page, expect } from '@playwright/test'; -import { Departments, SideNav, Global, LoginPage } from './pageobjects'; -import { adminLogin } from './utils/mocks/userAndPasswordMock'; +import { Auth, OmnichannelDepartaments } from './page-objects'; -test.describe('[Department]', () => { - let loginPage: LoginPage; - let sideNav: SideNav; - let departments: Departments; +test.describe('Department', () => { let page: Page; - let global: Global; + let pageAuth: Auth; + let pageOmnichannelDepartaments: OmnichannelDepartaments; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); - loginPage = new LoginPage(page); - sideNav = new SideNav(page); - departments = new Departments(page); - global = new Global(page); - - await page.goto('/'); - await loginPage.doLogin(adminLogin); - await sideNav.sidebarUserMenu.click(); - await sideNav.omnichannel.click(); + pageAuth = new Auth(page); + pageOmnichannelDepartaments = new OmnichannelDepartaments(page); }); - test.describe('[Render]', async () => { - test.beforeEach(async () => { - await departments.departmentsLink.click(); - await departments.btnNewDepartment.click(); - }); - - test('expect show all inputs', async () => { - await departments.getAddScreen(); - }); + test.beforeAll(async () => { + await pageAuth.doLogin(); + await page.goto('/omnichannel'); }); - test.describe('[Actions]', async () => { + test.describe('Actions', async () => { test.beforeEach(async () => { - await departments.departmentsLink.click(); + await pageOmnichannelDepartaments.departmentsLink.click(); }); - test.describe('[Create and Edit]', async () => { + test.describe('Create and Edit', async () => { test.afterEach(async () => { - await global.dismissToastBar(); + await pageOmnichannelDepartaments.btnToastClose.click(); }); test('expect new department is created', async () => { - await departments.btnNewDepartment.click(); - await departments.doAddDepartments(); - await expect(departments.departmentAdded).toBeVisible(); + await pageOmnichannelDepartaments.btnNewDepartment.click(); + await pageOmnichannelDepartaments.doAddDepartments(); + await expect(pageOmnichannelDepartaments.departmentAdded).toBeVisible(); }); test('expect department is edited', async () => { - await departments.departmentAdded.click(); - await departments.doEditDepartments(); - await expect(departments.departmentAdded).toHaveText('any_name_edit'); + await pageOmnichannelDepartaments.departmentAdded.click(); + await pageOmnichannelDepartaments.doEditDepartments(); + await expect(pageOmnichannelDepartaments.departmentAdded).toHaveText('any_name_edit'); }); }); - test.describe('[Delete department]', () => { + test.describe('Delete department', () => { test.beforeEach(async () => { - await departments.btnTableDeleteDepartment.click(); + await pageOmnichannelDepartaments.btnTableDeleteDepartment.click(); }); test('expect dont show dialog on cancel delete department', async () => { - await departments.btnModalCancelDeleteDepartment.click(); - await expect(departments.modalDepartment).not.toBeVisible(); - await expect(departments.departmentAdded).toBeVisible(); + await pageOmnichannelDepartaments.btnModalCancelDeleteDepartment.click(); + await expect(pageOmnichannelDepartaments.modalDepartment).not.toBeVisible(); + await expect(pageOmnichannelDepartaments.departmentAdded).toBeVisible(); }); test('expect delete departments', async () => { - await departments.btnModalDeleteDepartment.click(); - await expect(departments.modalDepartment).not.toBeVisible(); - await expect(departments.departmentAdded).not.toBeVisible(); + await pageOmnichannelDepartaments.btnModalDeleteDepartment.click(); + await expect(pageOmnichannelDepartaments.modalDepartment).not.toBeVisible(); + await expect(pageOmnichannelDepartaments.departmentAdded).not.toBeVisible(); }); }); }); diff --git a/apps/meteor/tests/e2e/pageobjects/PreferencesMainContent.ts b/apps/meteor/tests/e2e/page-objects/account-profile.ts similarity index 55% rename from apps/meteor/tests/e2e/pageobjects/PreferencesMainContent.ts rename to apps/meteor/tests/e2e/page-objects/account-profile.ts index d2545ca406fa..5e951a1d64ba 100644 --- a/apps/meteor/tests/e2e/pageobjects/PreferencesMainContent.ts +++ b/apps/meteor/tests/e2e/page-objects/account-profile.ts @@ -1,8 +1,12 @@ -import { Locator } from '@playwright/test'; +import { Locator, Page } from '@playwright/test'; -import { BasePage } from './BasePage'; +export class AccountProfile { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } -export class PreferencesMainContent extends BasePage { get inputName(): Locator { return this.page.locator('//label[contains(text(), "Name")]/..//input'); } @@ -11,23 +15,15 @@ export class PreferencesMainContent extends BasePage { 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 btnSubmit(): Locator { + return this.page.locator('[data-qa="AccountProfilePageSaveButton"]'); } 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"]'); + get emailTextInput(): Locator { + return this.page.locator('//label[contains(text(), "Email")]/..//input'); } } diff --git a/apps/meteor/tests/e2e/pageobjects/Administration.ts b/apps/meteor/tests/e2e/page-objects/administration.ts similarity index 96% rename from apps/meteor/tests/e2e/pageobjects/Administration.ts rename to apps/meteor/tests/e2e/page-objects/administration.ts index 5a742cec693b..541f1fcf281d 100644 --- a/apps/meteor/tests/e2e/pageobjects/Administration.ts +++ b/apps/meteor/tests/e2e/page-objects/administration.ts @@ -1,8 +1,20 @@ -import { Locator, expect } from '@playwright/test'; +import { Locator, expect, Page } from '@playwright/test'; -import { BasePage } from './BasePage'; +import { AdminFlextab, AdminSidenav } from './fragments'; + +export class Administration { + private readonly page: Page; + + tabs: AdminFlextab; + + sidenav: AdminSidenav; + + constructor(page: Page) { + this.page = page; + this.tabs = new AdminFlextab(page); + this.sidenav = new AdminSidenav(page); + } -export class Administration extends BasePage { get settingsSearch(): Locator { return this.page.locator('input[type=search]'); } diff --git a/apps/meteor/tests/e2e/page-objects/auth.ts b/apps/meteor/tests/e2e/page-objects/auth.ts new file mode 100644 index 000000000000..0a5cb5fbbd60 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/auth.ts @@ -0,0 +1,82 @@ +import { Locator, Page } from '@playwright/test'; + +import { ADMIN_CREDENTIALS } from '../utils/constants'; + +export class Auth { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + get toastSuccess(): Locator { + return this.page.locator('.rcx-toastbar.rcx-toastbar--success'); + } + + get toastError(): Locator { + return this.page.locator('.rcx-toastbar.rcx-toastbar--error'); + } + + get btnSubmit(): Locator { + return this.page.locator('.login'); + } + + get btnRegister(): Locator { + return this.page.locator('button.register'); + } + + get btnRegisterConfirmUsername(): Locator { + return this.page.locator('button[data-loading-text=" Please_wait ..."]'); + } + + get btnForgotPassword(): Locator { + return this.page.locator('.forgot-password'); + } + + get inputEmailOrUsername(): Locator { + return this.page.locator('[name=emailOrUsername]'); + } + + get inputName(): Locator { + return this.page.locator('[name=name]'); + } + + get textErrorName(): Locator { + return this.page.locator('[name=name]~.input-error'); + } + + get inputEmail(): Locator { + return this.page.locator('[name=email]'); + } + + get textErrorEmail(): Locator { + return this.page.locator('[name=email]~.input-error'); + } + + get inputPassword(): Locator { + return this.page.locator('[name=pass]'); + } + + get textErrorPassword(): Locator { + return this.page.locator('[name=pass]~.input-error'); + } + + get inputPasswordConfirm(): Locator { + return this.page.locator('[name=confirm-pass]'); + } + + get textErrorPasswordConfirm(): Locator { + return this.page.locator('[name=confirm-pass]~.input-error'); + } + + async doLogin(input = ADMIN_CREDENTIALS, shouldWaitForHome = true): Promise { + await this.page.goto('/'); + await this.page.locator('[name=emailOrUsername]').type(input.email); + await this.page.locator('[name=pass]').type(input.password); + await this.page.locator('.login').click(); + + if (shouldWaitForHome) { + await this.page.waitForSelector('text="Welcome to Rocket.Chat!"'); + } + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/admin-flextab.ts b/apps/meteor/tests/e2e/page-objects/fragments/admin-flextab.ts new file mode 100644 index 000000000000..7bbbe1a00b15 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/admin-flextab.ts @@ -0,0 +1,74 @@ +import { Locator, Page } from '@playwright/test'; + +export class AdminFlextab { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + get usersAddUserTab(): Locator { + return this.page.locator('//button[text()="New"]'); + } + + 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 usersAddUserVerifiedCheckbox(): Locator { + return this.page.locator('//label[text()="Email"]/following-sibling::span//input/following-sibling::i'); + } + + get usersAddUserPassword(): Locator { + return this.page.locator('//label[text()="Password"]/following-sibling::span//input'); + } + + get usersAddUserRoleList(): Locator { + return this.page.locator('//label[text()="Roles"]/following-sibling::span//input'); + } + + get usersButtonSave(): Locator { + return this.page.locator('//button[text()="Save"]'); + } + + get usersAddUserRandomPassword(): Locator { + return this.page.locator('//div[text()="Set random password and send by email"]/following-sibling::label//input'); + } + + 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 usersButtonCancel(): Locator { + return this.page.locator('//button[text()="Cancel"]'); + } + + get usersAddUserTabClose(): Locator { + return this.page.locator('//div[text()="Add User"]//button'); + } + + get addUserTable(): Locator { + return this.page.locator('//div[text()="Add User"]'); + } + + async doAddRole(role: string): Promise { + await this.usersAddUserRoleList.click(); + await this.page.locator(`li[value=${role}]`).click(); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/admin-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/admin-sidenav.ts new file mode 100644 index 000000000000..46b42b8c0cb7 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/admin-sidenav.ts @@ -0,0 +1,13 @@ +import { Locator, Page } from '@playwright/test'; + +export class AdminSidenav { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + get linkUsers(): Locator { + return this.page.locator('.flex-nav [href="/admin/users"]'); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts new file mode 100644 index 000000000000..7ff34eb718f5 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -0,0 +1,200 @@ +import fs from 'fs/promises'; + +import { Locator, Page } from '@playwright/test'; + +export class HomeContent { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + get btnAudioRecod(): Locator { + return this.page.locator('[data-qa-id="audio-record"]'); + } + + get waitForLastMessageTextAttachmentEqualsText(): Locator { + return this.page.locator('[data-qa-type="message"]:last-child .rcx-attachment__details .rcx-box--with-inline-elements'); + } + + get lastMessageRoleAdded(): Locator { + return this.page.locator('[data-qa="system-message"] [data-qa-type="system-message-body"]').last(); + } + + get lastMessage(): Locator { + return this.page.locator('.messages-box [data-qa-type="message"]').last(); + } + + get lastUserMessage(): Locator { + return this.page.locator('[data-qa-type="message"]').last(); + } + + get lastUserMessageNotSequential(): Locator { + return this.page.locator('[data-qa-type="message"][data-sequential="false"]').last(); + } + + get userCardLinkProfile(): Locator { + return this.page.locator('[data-qa="UserCard"] a'); + } + + 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 inputMain(): Locator { + return this.page.locator('[name="msg"]'); + } + + get btnSend(): Locator { + return this.page.locator('.rc-message-box__icon.js-send'); + } + + get messagePopUpFirstItem(): Locator { + return this.page.locator('.popup-item.selected'); + } + + get emojiBtn(): Locator { + return this.page.locator('.rc-message-box__icon.emoji-picker-icon'); + } + + get emojiPickerPeopleIcon(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "icon-people")]'); + } + + get emojiGrinning(): Locator { + return this.page.locator('//*[contains(@class, "emoji-picker")]//*[contains(@class, "emoji-grinning")]'); + } + + get lastMessageForMessageTest(): Locator { + return this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="message-body"]'); + } + + channelTitle(title: string): Locator { + return this.page.locator('.rcx-room-header', { hasText: title }); + } + + get modalCancelButton(): Locator { + return this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--secondary'); + } + + 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 buttonSend(): Locator { + return this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--primary'); + } + + get descriptionInput(): Locator { + return this.page.locator('//div[@id="modal-root"]//fieldset//div[2]//span//input'); + } + + get getFileDescription(): Locator { + return this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="attachment-description"]'); + } + + get fileNameInput(): Locator { + return this.page.locator('//div[@id="modal-root"]//fieldset//div[1]//span//input'); + } + + get lastMessageFileName(): Locator { + return this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="attachment-title-link"]'); + } + + async setTextToInput(text: string, options: { delay?: number } = {}): Promise { + await this.page.locator('[name="msg"]').click({ clickCount: 3 }); + await this.page.keyboard.press('Backspace'); + await this.page.locator('[name="msg"]').type(text, { delay: options.delay ?? 0 }); + } + + async doSendMessage(text: string): Promise { + await this.page.locator('[name="msg"]').type(text); + await this.page.keyboard.press('Enter'); + } + + async doOpenMessageActionMenu(): Promise { + await this.page.locator('[data-qa-type="message"]:last-child').hover(); + await this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="message-action-menu"][data-qa-id="menu"]').waitFor(); + await this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="message-action-menu"][data-qa-id="menu"]').click(); + } + + async doReload(): Promise { + await this.page.reload({ waitUntil: 'load' }); + await this.page.waitForSelector('.messages-box'); + } + + async openMoreActionMenu(): Promise { + await this.page.locator('.rc-message-box [data-qa-id="menu-more-actions"]').click(); + await this.page.waitForSelector('.rc-popover__content'); + } + + async doSelectAction( + action: 'edit' | 'reply' | 'delete' | 'permalink' | 'copy' | 'quote' | 'star' | 'unread' | 'reaction', + ): Promise { + switch (action) { + case 'edit': + await this.page.locator('[data-qa-id="edit-message"]').click(); + await this.page.locator('[name="msg"]').fill('this message was edited'); + await this.page.keyboard.press('Enter'); + break; + case 'reply': + await this.page.locator('[data-qa-id="reply-in-thread"]').click(); + break; + case 'delete': + await this.page.locator('[data-qa-id="delete-message"]').click(); + await this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--danger').click(); + break; + case 'permalink': + await this.page.locator('[data-qa-id="permalink"]').click(); + break; + case 'copy': + await this.page.locator('[data-qa-id="copy"]').click(); + break; + case 'quote': + await this.page.locator('[data-qa-id="quote-message"]').click(); + await this.page.locator('[name="msg"]').type('this is a quote message'); + await this.page.keyboard.press('Enter'); + break; + case 'star': + await this.page.locator('[data-qa-id="star-message"]').click(); + break; + case 'unread': + await this.page.locator('[data-id="mark-message-as-unread"][data-type="message-action"]').click(); + break; + case 'reaction': + await this.page.locator('[data-qa-id="reply-in-thread"]').click(); + await this.emojiPickerPeopleIcon.click(); + await this.emojiGrinning.click(); + break; + } + } + + async doDragAndDropFile(): Promise { + const contract = await fs.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 }, + ); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts new file mode 100644 index 000000000000..12cfbe0ef874 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts @@ -0,0 +1,114 @@ +import { Locator, Page } from '@playwright/test'; + +export class HomeFlextab { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + get userInfoUsername(): Locator { + return this.page.locator('[data-qa="UserInfoUserName"]'); + } + + get btnTabMembers(): Locator { + return this.page.locator('[data-qa-id=ToolBoxAction-members]'); + } + + get editNameBtn(): Locator { + return this.page.locator('//aside//button[contains(text(), "Edit")]'); + } + + get btnTabInfo(): Locator { + return this.page.locator('[data-qa-id=ToolBoxAction-info-circled]'); + } + + get mainSideBar(): Locator { + return this.page.locator('//main//aside'); + } + + get mainSideBarClose(): Locator { + return this.page.locator('//main//aside/h3//i[contains(@class, "rcx-icon--name-cross")]/..'); + } + + get editTopicTextInput(): Locator { + return this.page.locator('//main//aside//label[contains(text(), "Topic")]/..//textarea'); + } + + get editNameSave(): Locator { + return this.page.locator('//aside//button[contains(text(), "Save")]'); + } + + secondSetting(topic: string): Locator { + return this.page.locator(`//header//*[contains(text(), "${topic}")]`); + } + + get editAnnouncementTextInput(): Locator { + return this.page.locator('//main//aside//label[contains(text(), "Announcement")]/..//textarea'); + } + + get thirdSetting(): Locator { + return this.page.locator('[data-qa="AnnouncementAnnoucementComponent"] div:nth-child(1)'); + } + + get editDescriptionTextInput(): Locator { + return this.page.locator('//main//aside//label[contains(text(), "Description")]/..//textarea'); + } + + get mainSideBarBack(): Locator { + return this.page.locator('(//main//aside/h3//button)[1]'); + } + + get fourthSetting(): Locator { + return this.page.locator('//main//aside//div[contains(text(), "Description")]//following-sibling::div'); + } + + get firstSetting(): Locator { + return this.page.locator('//aside//i[contains(@class, "rcx-icon--name-hashtag")]/../div'); + } + + get editNameTextInput(): Locator { + return this.page.locator('//aside//label[contains(text(), "Name")]/..//input'); + } + + get messageInput(): Locator { + return this.page.locator('.rcx-vertical-bar .js-input-message'); + } + + 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"]', + ); + } + + 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]'); + } + + async doAddPeopleToChannel(user: string): Promise { + await this.page.locator('//button[contains(text(), "Add")]').click(); + await this.page.locator('//label[contains(text(), "Choose users")]/..//input').type(user); + await this.page.waitForTimeout(3000); + await this.page.locator('[data-qa-type="autocomplete-user-option"]').click(); + await this.page.locator('//button[contains(text(), "Add users")]').click(); + } + + async doMuteUser(username: string): Promise { + await this.page.locator(`[data-qa="MemberItem-${username}"]`).click(); + await this.page.locator('[data-qa="UserUserInfo-menu"]').click(); + await this.page.locator('[value="muteUser"]').click(); + await this.page.locator('.rcx-modal .rcx-button--danger').click(); + await this.page.locator('(//main//aside/h3//button)[1]').click(); + } + + async doSetUserOwner(username: string): Promise { + await this.page.locator(`[data-qa="MemberItem-${username}"]`).click(); + await this.page.locator('//main//aside//button[contains(text(), "Set as owner")]').click(); + } + + async doSetUserModerator(username: string): Promise { + await this.page.locator(`[data-qa="MemberItem-${username}"]`).click(); + await this.page.locator('[data-qa="UserUserInfo-menu"]').click(); + await this.page.locator('[value="changeModerator"]').click(); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts new file mode 100644 index 000000000000..bdfae3f7c850 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -0,0 +1,82 @@ +import { Page, Locator } from '@playwright/test'; + +export class HomeSidenav { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + get btnAvatar(): Locator { + return this.page.locator('[data-qa="sidebar-avatar-button"]'); + } + + get linkAdmin(): Locator { + return this.page.locator('//li[@class="rcx-option"]//div[contains(text(), "Administration")]'); + } + + get linkAccount(): Locator { + return this.page.locator('//li[@class="rcx-option"]//div[contains(text(), "My Account")]'); + } + + get btnCreate(): Locator { + return this.page.locator('[data-qa="sidebar-create"]'); + } + + get checkboxChannelType(): Locator { + return this.page.locator( + '//*[@id="modal-root"]//*[contains(@class, "rcx-field") and contains(text(), "Private")]/../following-sibling::label/i', + ); + } + + get inputChannelName(): Locator { + return this.page.locator('#modal-root [placeholder="Channel Name"]'); + } + + get btnCreateChannel(): Locator { + return this.page.locator('//*[@id="modal-root"]//button[contains(text(), "Create")]'); + } + + createOptionByText(text: string): Locator { + return this.page.locator(`li.rcx-option >> text="${text}"`); + } + + get channelName(): Locator { + return this.page.locator('#modal-root [placeholder="Channel Name"]'); + } + + get channelType(): Locator { + return this.page.locator( + '//*[@id="modal-root"]//*[contains(@class, "rcx-field") and contains(text(), "Private")]/../following-sibling::label/i', + ); + } + + async doOpenChat(name: string): Promise { + await this.page.locator('[data-qa="sidebar-search"]').click(); + await this.page.locator('[data-qa="sidebar-search-input"]').type(name); + await this.page.locator('[data-qa="sidebar-item-title"]', { hasText: name }).first().click(); + } + + async doOpenProfile(): Promise { + await this.page.locator('[data-qa="sidebar-avatar-button"]').click(); + await this.page.locator('//li[@class="rcx-option"]//div[contains(text(), "My Account")]').click(); + } + + async doLogout(): Promise { + await this.page.goto('/home'); + await this.page.locator('[data-qa="sidebar-avatar-button"]').click(); + await this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Logout")]').click(); + } + + async doCreateChannel(channelName: string, isPrivate = false): Promise { + await this.page.locator('[data-qa="sidebar-create"]').click(); + await this.page.locator('li.rcx-option >> text="Channel"').click(); + + if (!isPrivate) { + await this.channelType.click(); + } + + await this.channelName.type(channelName); + await this.page.locator('//*[@id="modal-root"]//button[contains(text(), "Create")]').click(); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/index.ts b/apps/meteor/tests/e2e/page-objects/fragments/index.ts new file mode 100644 index 000000000000..b968b92e5f44 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/index.ts @@ -0,0 +1,5 @@ +export * from './admin-flextab'; +export * from './admin-sidenav'; +export * from './home-content'; +export * from './home-flextab'; +export * from './home-sidenav'; diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts new file mode 100644 index 000000000000..5b865ff2ef01 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -0,0 +1,26 @@ +import { Page } from '@playwright/test'; + +import { HomeContent, HomeSidenav, HomeFlextab } from './fragments'; + +export class HomeChannel { + private readonly page: Page; + + readonly content: HomeContent; + + readonly sidenav: HomeSidenav; + + readonly tabs: HomeFlextab; + + constructor(page: Page) { + this.page = page; + this.content = new HomeContent(page); + this.sidenav = new HomeSidenav(page); + this.tabs = new HomeFlextab(page); + } + + async doDismissToast(): Promise { + if (await this.page.locator('.rcx-toastbar').isVisible()) { + await this.page.locator('.rcx-toastbar').locator('button').click(); + } + } +} diff --git a/apps/meteor/tests/e2e/pageobjects/Discussion.ts b/apps/meteor/tests/e2e/page-objects/home-discussion.ts similarity index 75% rename from apps/meteor/tests/e2e/pageobjects/Discussion.ts rename to apps/meteor/tests/e2e/page-objects/home-discussion.ts index 18dcaf6910ba..11d3a9cde2fa 100644 --- a/apps/meteor/tests/e2e/pageobjects/Discussion.ts +++ b/apps/meteor/tests/e2e/page-objects/home-discussion.ts @@ -1,8 +1,24 @@ -import { Locator, expect } from '@playwright/test'; +import { Locator, Page, expect } from '@playwright/test'; -import { BasePage } from './BasePage'; +import { HomeContent, HomeSidenav, HomeFlextab } from './fragments'; + +export class HomeDiscussion { + private readonly page: Page; + + readonly content: HomeContent; + + readonly sidenav: HomeSidenav; + + readonly tabs: HomeFlextab; + + constructor(page: Page) { + this.page = page; + + this.content = new HomeContent(page); + this.sidenav = new HomeSidenav(page); + this.tabs = new HomeFlextab(page); + } -export class Discussion extends BasePage { get startDiscussionContextItem(): Locator { return this.page.locator('[data-qa-id="start-discussion"][data-qa-type="message-action"]'); } @@ -44,7 +60,7 @@ export class Discussion extends BasePage { await expect(this.discussionCreated(discussionName)).toBeVisible(); } - async createDiscussionInContext(message: string): Promise { + async doCreateDiscussionInContext(message: string): Promise { await this.startDiscussionContextItem.waitFor(); await this.page.pause(); await this.startDiscussionContextItem.click(); diff --git a/apps/meteor/tests/e2e/page-objects/index.ts b/apps/meteor/tests/e2e/page-objects/index.ts new file mode 100644 index 000000000000..9d1a69fd41d2 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/index.ts @@ -0,0 +1,7 @@ +export * from './account-profile'; +export * from './administration'; +export * from './auth'; +export * from './home-channel'; +export * from './home-discussion'; +export * from './omnichannel-agents'; +export * from './omnichannel-departaments'; diff --git a/apps/meteor/tests/e2e/pageobjects/Agents.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-agents.ts similarity index 84% rename from apps/meteor/tests/e2e/pageobjects/Agents.ts rename to apps/meteor/tests/e2e/page-objects/omnichannel-agents.ts index 8964aea58f5b..9026b2e6bd89 100644 --- a/apps/meteor/tests/e2e/pageobjects/Agents.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-agents.ts @@ -1,9 +1,12 @@ -import { Locator, expect } from '@playwright/test'; +import { Locator, expect, Page } from '@playwright/test'; -import { BACKSPACE } from '../utils/mocks/keyboardKeyMock'; -import { BasePage } from './BasePage'; +export class OmnichannelAgents { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } -export class Agents extends BasePage { get agentsLink(): Locator { return this.page.locator('a[href="omnichannel/agents"]'); } @@ -78,6 +81,18 @@ export class Agents extends BasePage { return this.page.locator('[data-qa="AgentEditButtonSave"]'); } + get modal(): Locator { + return this.page.locator('#modal-root'); + } + + get btnModalCancel(): Locator { + return this.page.locator('#modal-root dialog .rcx-modal__inner .rcx-modal__footer .rcx-button--secondary'); + } + + get btnModalRemove(): Locator { + return this.page.locator('#modal-root dialog .rcx-modal__inner .rcx-modal__footer .rcx-button--danger'); + } + getAgentInputs(id: string): Locator { return this.page.locator(`[data-qa="AgentEditTextInput-${id}"]`); } @@ -87,7 +102,7 @@ export class Agents extends BasePage { await this.inputAgentsUserName.type('rocket.cat', { delay: 100 }); // FIXME: temp solution for rocket.chat instability await this.page.waitForTimeout(2000); - await this.keyboardPress(BACKSPACE); + await this.page.keyboard.press('Backspace'); await this.userOption.click(); await this.btnAddAgents.click(); diff --git a/apps/meteor/tests/e2e/pageobjects/Departments.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-departaments.ts similarity index 81% rename from apps/meteor/tests/e2e/pageobjects/Departments.ts rename to apps/meteor/tests/e2e/page-objects/omnichannel-departaments.ts index 4b97a46cc1a1..b5cf8b084215 100644 --- a/apps/meteor/tests/e2e/pageobjects/Departments.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-departaments.ts @@ -1,8 +1,16 @@ -import { Locator, expect } from '@playwright/test'; +import { Locator, Page } from '@playwright/test'; -import { BasePage } from './BasePage'; +export class OmnichannelDepartaments { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + get btnToastClose(): Locator { + return this.page.locator('.rcx-toastbar').locator('button'); + } -export class Departments extends BasePage { get departmentsLink(): Locator { return this.page.locator('a[href="omnichannel/departments"]'); } @@ -83,16 +91,6 @@ export class Departments extends BasePage { return this.page.locator('#modal-root'); } - async getAddScreen(): Promise { - const textInputs = [this.nameInput, this.descriptionInput, this.emailInput]; - const toggleButtons = [this.enabledToggle, this.showOnOfflinePageToggle, this.requestTagBeforeClosingChatToggle]; - const selects = [this.selectLiveChatDepartmentOfflineMessageToChannel, this.selectAgentsTable]; - const actionsButtons = [this.btnSaveDepartment, this.btnBack, this.btnAddAgent]; - const addScreenSelectors = [...textInputs, ...toggleButtons, ...actionsButtons, ...selects]; - - await Promise.all(addScreenSelectors.map((addScreenSelector) => expect(addScreenSelector).toBeVisible())); - } - async doAddAgent(): Promise { await this.enabledToggle.click(); await this.nameInput.type('rocket.cat'); @@ -115,10 +113,10 @@ export class Departments extends BasePage { async doEditDepartments(): Promise { await this.enabledToggle.click(); await this.nameInput.click({ clickCount: 3 }); - await this.keyboardPress('Backspace'); + await this.page.keyboard.press('Backspace'); await this.nameInput.fill('any_name_edit'); await this.descriptionInput.click({ clickCount: 3 }); - await this.keyboardPress('Backspace'); + await this.page.keyboard.press('Backspace'); await this.descriptionInput.fill('any_description_edited'); await this.btnSaveDepartment.click(); } diff --git a/apps/meteor/tests/e2e/pageobjects/BasePage.ts b/apps/meteor/tests/e2e/pageobjects/BasePage.ts deleted file mode 100644 index 7e14b2470f56..000000000000 --- a/apps/meteor/tests/e2e/pageobjects/BasePage.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Page } from '@playwright/test'; - -export class BasePage { - public page: Page; - - constructor(page: Page) { - this.page = page; - } - - async keyboardPress(key: string): Promise { - await this.page.keyboard.press(key); - } -} diff --git a/apps/meteor/tests/e2e/pageobjects/FlexTab.ts b/apps/meteor/tests/e2e/pageobjects/FlexTab.ts deleted file mode 100644 index aaa4c3a7d21a..000000000000 --- a/apps/meteor/tests/e2e/pageobjects/FlexTab.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { Locator } from '@playwright/test'; - -import { BasePage } from './BasePage'; - -export class FlexTab extends BasePage { - 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 messageInput(): Locator { - return this.page.locator('.rcx-vertical-bar .js-input-message'); - } - - get btnTabInfo(): Locator { - return this.page.locator('[data-qa-id=ToolBoxAction-info-circled]'); - } - - get btnTabSearch(): Locator { - return this.page.locator('[data-qa-id=ToolBoxAction-magnifier]'); - } - - get btnTabMembers(): Locator { - return this.page.locator('[data-qa-id=ToolBoxAction-members]'); - } - - get btnTabNotifications(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Notifications Preferences")]'); - } - - get btnTabFiles(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Files")]'); - } - - get btnTabMentions(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Mentions")]'); - } - - get btnTabStared(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Starred Messages")]'); - } - - get btnTabPinned(): Locator { - return this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Pinned Messages")]'); - } - - get contentTabInfo(): 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 contentTabMembers(): Locator { - return this.page.locator('[data-qa-id=RoomHeader-Members]'); - } - - get contentTabNotifications(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-box--full.rcx-icon--name-bell'); - } - - get contentTabSearch(): Locator { - return this.page.locator('#message-search'); - } - - get contentTabFiles(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-icon--name-attachment'); - } - - get contentTabMentions(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-icon--name-at'); - } - - get contentTabStared(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-icon--name-star'); - } - - get contentTabPinned(): Locator { - return this.page.locator('aside > h3 > div > i.rcx-icon--name-pin'); - } - - 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 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 userUsername(): Locator { - return this.page.locator('[data-qa="UserInfoUserName"]'); - } - - get searchResult(): Locator { - return this.page.locator('.new-day'); - } - - 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 firstSetting(): Locator { - return this.page.locator('//aside//i[contains(@class, "rcx-icon--name-hashtag")]/../div'); - } - - 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]'); - } - - 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('[data-qa-type="autocomplete-user-option"]'); - } - - get userMoreActions(): Locator { - return this.page.locator('[data-qa="UserUserInfo-menu"]'); - } - - async setUserOwner(user: string): Promise { - await this.enterUserView(user); - await this.setOwnerBtn.waitFor(); - await this.setOwnerBtn.click(); - } - - async setUserModerator(user: string): Promise { - await this.enterUserView(user); - await this.userMoreActions.click(); - await this.setModeratorBtn.waitFor(); - await this.setModeratorBtn.click(); - } - - async muteUser(user: string): Promise { - await this.enterUserView(user); - await this.userMoreActions.click(); - await this.muteUserBtn.waitFor(); - await this.muteUserBtn.click(); - await this.page.locator('.rcx-modal .rcx-button--danger').click(); - await this.mainSideBarBack.click(); - } - - async enterUserView(user: string): Promise { - const userEl = this.getUserEl(user); - await userEl.waitFor(); - await userEl.click(); - } - - 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(); - } - - 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"]', - ); - } - - async doAddRole(role: string): Promise { - await this.usersAddUserRoleList.click(); - await this.page.locator(`li[value=${role}]`).click(); - } - - async doOpenMoreOptionMenu(): Promise { - await this.page.locator('[data-qa-id=ToolBox-Menu]').click({ force: true, clickCount: 3 }); - } -} diff --git a/apps/meteor/tests/e2e/pageobjects/Global.ts b/apps/meteor/tests/e2e/pageobjects/Global.ts deleted file mode 100644 index dc4e9318c351..000000000000 --- a/apps/meteor/tests/e2e/pageobjects/Global.ts +++ /dev/null @@ -1,50 +0,0 @@ -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--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'); - } - - async confirmPopup(): Promise { - await this.modalConfirm.waitFor(); - await this.modalConfirm.click(); - } - - 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 dialog .rcx-modal__inner .rcx-modal__footer .rcx-button--secondary'); - } - - get btnModalRemove(): Locator { - return this.page.locator('#modal-root dialog .rcx-modal__inner .rcx-modal__footer .rcx-button--danger'); - } -} diff --git a/apps/meteor/tests/e2e/pageobjects/LoginPage.ts b/apps/meteor/tests/e2e/pageobjects/LoginPage.ts deleted file mode 100644 index ce17487adbe6..000000000000 --- a/apps/meteor/tests/e2e/pageobjects/LoginPage.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Locator } 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 { - get btnRegister(): Locator { - return this.page.locator('button.register'); - } - - get btnForgotPassword(): Locator { - return this.page.locator('.forgot-password'); - } - - get btnSubmit(): 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"]'); - } - - 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.btnSubmit.click(); - - await this.page.waitForSelector(REGISTER_STEP2_BUTTON); - await this.registerNextButton.click(); - await this.page.waitForSelector(HOME_SELECTOR); - } - - async doLogin({ email, password }: ILogin, shouldWaitForHomeScreen = true): Promise { - await this.emailOrUsernameField.type(email); - await this.passwordField.type(password); - await this.btnSubmit.click(); - - if (shouldWaitForHomeScreen) { - await this.page.waitForSelector('.main-content'); - } - } -} diff --git a/apps/meteor/tests/e2e/pageobjects/MainContent.ts b/apps/meteor/tests/e2e/pageobjects/MainContent.ts deleted file mode 100644 index 6cef461a6d2f..000000000000 --- a/apps/meteor/tests/e2e/pageobjects/MainContent.ts +++ /dev/null @@ -1,350 +0,0 @@ -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")]'); - } - - 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 lastUserMessage(): Locator { - return this.page.locator('[data-qa-type="message"][data-sequential="false"]').last().locator('[data-qa-type="username"]'); - } - - get btnLastUserMessage(): Locator { - return this.page.locator('[data-qa-type="message"][data-sequential="false"]').last().locator('[data-qa-type="username"]'); - } - - get lastMessageFileName(): Locator { - return this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="attachment-title-link"]'); - } - - get lastMessage(): Locator { - return this.page.locator('.messages-box [data-qa-type="message"]').last(); - } - - get lastMessageRoleAdded(): Locator { - const roleAddedMessageRegex = new RegExp(/\badded by\b/g); - return this.page.locator('[data-qa="system-message"] [data-qa-type="system-message-body"]', { hasText: roleAddedMessageRegex }); - } - - 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 [data-qa-type="message-body"]'); - } - - 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--secondary'); - } - - get modalDeleteMessageButton(): Locator { - return this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--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'); - } - - async waitForLastMessageEqualsHtml(text: string): Promise { - await expect(this.lastMessageForMessageTest).toContainText(text); - } - - async waitForLastMessageEqualsText(text: string): Promise { - await expect(this.lastMessage).toContainText(text); - } - - async sendMessage(text: string): Promise { - await this.setTextToInput(text); - await this.keyboardPress('Enter'); - } - - async addTextToInput(text: any): Promise { - await this.messageInput.type(text); - } - - 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 }); - } - - 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 }, - ); - } - - 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'); - } - - async setFileName(): Promise { - await this.fileNameInput.fill('any_file1.txt'); - } - - async setDescription(): Promise { - await this.descriptionInput.type('any_description'); - } - - get getFileDescription(): Locator { - return this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="attachment-description"]'); - } - - 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; - } - } - - async openMessageActionMenu(): Promise { - await this.page.locator('[data-qa-type="message"]:last-child').hover(); - await this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="message-action-menu"][data-qa-id="menu"]').waitFor(); - await this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="message-action-menu"][data-qa-id="menu"]').click(); - } - - async openMoreActionMenu(): Promise { - await this.page.locator('.rc-message-box [data-qa-id="menu-more-actions"]').click(); - await this.page.waitForSelector('.rc-popover__content'); - } - - 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'); - } - - async doReload(): Promise { - await this.page.reload({ waitUntil: 'load' }); - await this.page.waitForSelector('.messages-box'); - } -} diff --git a/apps/meteor/tests/e2e/pageobjects/SetupWizard.ts b/apps/meteor/tests/e2e/pageobjects/SetupWizard.ts deleted file mode 100644 index 4e2d5a13fc92..000000000000 --- a/apps/meteor/tests/e2e/pageobjects/SetupWizard.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { expect, Locator } from '@playwright/test'; - -import { BasePage } from './BasePage'; -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('//a[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")]'); - } - - async stepTwoSuccess(): Promise { - await this.organizationName.type('rocket.chat.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(); - } - - async stepThreeSuccess(): Promise { - await this.standaloneServer.click(); - } - - 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(); - } - - async stepOneFailedWithInvalidEmail(adminCredentials: IRegister): Promise { - await this.fullName.type(adminCredentials.name); - await this.userName.type(adminCredentials.name); - await this.companyEmail.type('mail'); - await this.password.type(adminCredentials.password); - await this.nextStep.click(); - await expect(this.companyEmail).toBeFocused(); - } - - 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(); - } - - async stepThreeFailedWithInvalidField(): Promise { - await this.registeredServer.type('mail'); - 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 deleted file mode 100644 index b15a2a230e95..000000000000 --- a/apps/meteor/tests/e2e/pageobjects/SideNav.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { expect, Locator } from '@playwright/test'; - -import { BasePage } from './BasePage'; - -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 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 btnSidebarCreate(): 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.page.locator('[data-qa="sidebar-item-title"]', { hasText: '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"]'); - } - - async isSideBarOpen(): Promise { - return !!(await this.sideNavBar.getAttribute('style')); - } - - async doOpenChat(name: string): Promise { - await expect(this.page.locator('[data-qa="sidebar-search"]')).toBeVisible(); - - await this.page.locator('[data-qa="sidebar-search"]').click(); - await this.page.locator('[data-qa="sidebar-search-input"]').type(name); - await this.page.locator('[data-qa="sidebar-item-title"]', { hasText: name }).first().click(); - } - - async doCreateChannel(channelName: string, isPrivate = false): Promise { - await this.page.locator('[data-qa="sidebar-create"]').click(); - await this.page.locator('li.rcx-option >> text="Channel"').click(); - - if (!isPrivate) { - await this.channelType.click(); - } - - await this.channelName.type(channelName); - await this.page.locator('//*[@id="modal-root"]//button[contains(text(), "Create")]').click(); - } - - 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 deleted file mode 100644 index f84d31c84453..000000000000 --- a/apps/meteor/tests/e2e/pageobjects/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './Administration'; -export * from './Agents'; -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/constants.ts b/apps/meteor/tests/e2e/utils/constants.ts index 1e9944d1d7b9..740a95d23a37 100644 --- a/apps/meteor/tests/e2e/utils/constants.ts +++ b/apps/meteor/tests/e2e/utils/constants.ts @@ -1,7 +1,14 @@ export const BASE_URL = process.env.BASE_URL ?? 'http://localhost:3000'; +export const BASE_API_URL = `${BASE_URL}/api/v1`; + export const IS_LOCALHOST = BASE_URL.startsWith('http://localhost'); export const IS_EE = Boolean(process.env.IS_EE); export const URL_MONGODB = process.env.MONGO_URL || 'mongodb://localhost:3001/meteor?retryWrites=false'; + +export const ADMIN_CREDENTIALS = { + email: 'rocketchat.internal.admin.test@rocket.chat', + password: 'rocketchat.internal.admin.test', +}; diff --git a/apps/meteor/tests/e2e/utils/mocks/keyboardKeyMock.ts b/apps/meteor/tests/e2e/utils/mocks/keyboardKeyMock.ts deleted file mode 100644 index 035611d3ee70..000000000000 --- a/apps/meteor/tests/e2e/utils/mocks/keyboardKeyMock.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const ENTER = 'Enter'; -export const BACKSPACE = 'Backspace'; diff --git a/apps/meteor/tests/e2e/utils/mocks/urlMock.ts b/apps/meteor/tests/e2e/utils/mocks/urlMock.ts deleted file mode 100644 index 78e91b138af0..000000000000 --- a/apps/meteor/tests/e2e/utils/mocks/urlMock.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const LOCALHOST = 'localhost:3000'; - -export const BASE_API_URL = `http://${process.env.TEST_API_URL ?? LOCALHOST}/api/v1`; - -export const setupWizardStepRegex = { - _1: /.*\/setup-wizard\/1/, - _2: /.*\/setup-wizard\/2/, - _3: /.*\/setup-wizard\/3/, -}; diff --git a/apps/meteor/tests/e2e/utils/mocks/userAndPasswordMock.ts b/apps/meteor/tests/e2e/utils/mocks/userAndPasswordMock.ts index fcb4d6da6879..72b4df140e15 100644 --- a/apps/meteor/tests/e2e/utils/mocks/userAndPasswordMock.ts +++ b/apps/meteor/tests/e2e/utils/mocks/userAndPasswordMock.ts @@ -28,13 +28,3 @@ export const createRegisterUser = (): IRegister => { username: faker.internet.userName(), }; }; - -export const validUser: ILogin = { - email: registerUser.email, - password: 'any_password', -}; - -export const incorrectUser: ILogin = { - email: faker.internet.email(), - password: 'any_password', -}; diff --git a/apps/meteor/tests/e2e/utils/mocks/waitSelectorsMock.ts b/apps/meteor/tests/e2e/utils/mocks/waitSelectorsMock.ts deleted file mode 100644 index 783d2f6e7559..000000000000 --- a/apps/meteor/tests/e2e/utils/mocks/waitSelectorsMock.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const HOME_SELECTOR = '//span[@class="rc-header__block"]'; -export const REGISTER_STEP2_BUTTON = '//button[contains(text(), "Use this username")]'; -export const ROCKET_CAT_SELECTOR = '//table//tbody//tr[1]//td//div//div//div//div[text()="Rocket.Cat"]'; diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 85ed38cf543c..5ef8b603990f 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -1,32 +1,43 @@ import { expect } from 'chai'; +import { IOmnichannelRoom, IVisitor } from '@rocket.chat/core-typings'; +import { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../../data/api-data.js'; -import { createVisitor, createLivechatRoom, createAgent } from '../../../data/livechat/rooms.js'; +import { createVisitor, createLivechatRoom, createAgent, makeAgentAvailable } from '../../../data/livechat/rooms.js'; import { updatePermission, updateSetting } from '../../../data/permissions.helper'; describe('LIVECHAT - rooms', function () { this.retries(0); + let visitor: IVisitor; + let room: IOmnichannelRoom; before((done) => getCredentials(done)); before((done) => { updateSetting('Livechat_enabled', true).then(() => { createAgent() + .then(() => makeAgentAvailable()) .then(() => createVisitor()) - .then((visitor) => createLivechatRoom(visitor.token)) - .then(() => done()); + .then((createdVisitor) => { + visitor = createdVisitor; + return createLivechatRoom(createdVisitor.token); + }) + .then((createdRoom) => { + room = createdRoom; + done(); + }); }); }); describe('livechat/rooms', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { - updatePermission('view-livechat-manager', []).then(() => { + updatePermission('view-livechat-rooms', []).then(() => { request .get(api('livechat/rooms')) .set(credentials) .expect('Content-Type', 'application/json') .expect(403) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); expect(res.body.error).to.be.equal('unauthorized'); }) @@ -34,13 +45,13 @@ describe('LIVECHAT - rooms', function () { }); }); it('should return an error when the "agents" query parameter is not valid', (done) => { - updatePermission('view-livechat-manager', ['admin']).then(() => { + updatePermission('view-livechat-rooms', ['admin']).then(() => { request .get(api('livechat/rooms?agents=invalid')) .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -52,7 +63,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -63,7 +74,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -74,7 +85,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -85,7 +96,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -96,7 +107,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -107,7 +118,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -118,7 +129,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -129,7 +140,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(200) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', true); expect(res.body.rooms).to.be.an('array'); expect(res.body).to.have.property('offset'); @@ -148,7 +159,7 @@ describe('LIVECHAT - rooms', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(200) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', true); expect(res.body.rooms).to.be.an('array'); expect(res.body).to.have.property('offset'); @@ -162,11 +173,15 @@ describe('LIVECHAT - rooms', function () { describe('livechat/room.close', () => { it('should return an "invalid-token" error when the visitor is not found due to an invalid token', (done) => { request - .get(api('livechat/room.close')) + .post(api('livechat/room.close')) .set(credentials) + .send({ + token: 'invalid-token', + rid: room._id, + }) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); expect(res.body.error).to.be.equal('invalid-token'); }) @@ -175,64 +190,52 @@ describe('LIVECHAT - rooms', function () { it('should return an "invalid-room" error when the room is not found due to invalid token and/or rid', (done) => { request - .get(api('livechat/room.close')) + .post(api('livechat/room.close')) .set(credentials) + .send({ + token: visitor.token, + rid: 'invalid-rid', + }) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); expect(res.body.error).to.be.equal('invalid-room'); }) .end(done); }); - it('should return an "room-closed" error when the room is already closed', (done) => { + it('should return both the rid and the comment of the room when the query params is all valid', (done) => { request - .get(api('livechat/room.close')) + .post(api(`livechat/room.close`)) .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('room-closed'); + .send({ + token: visitor.token, + rid: room._id, }) - .end(done); - }); - - it('should return an error when the "rid" query parameter is not valid', (done) => { - request - .get(api('livechat/rooms?rid=invalid')) - .set(credentials) .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rid'); + expect(res.body).to.have.property('comment'); }) .end(done); }); - it('should return an error when the "token" query parameter is not valid', (done) => { + it('should return an "room-closed" error when the room is already closed', (done) => { request - .get(api('livechat/rooms?token=invalid')) + .post(api('livechat/room.close')) .set(credentials) + .send({ + token: visitor.token, + rid: room._id, + }) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - - it('should return both the rid and the comment of the room when the query params is all valid', (done) => { - request - .get(api(`livechat/room.close?rid=123&token=321`)) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('rid'); - expect(res.body).to.have.property('comment'); + expect(res.body.error).to.be.equal('room-closed'); }) .end(done); }); diff --git a/apps/meteor/tests/end-to-end/api/livechat/01-department.js b/apps/meteor/tests/end-to-end/api/livechat/01-department.js deleted file mode 100644 index 224a5016b937..000000000000 --- a/apps/meteor/tests/end-to-end/api/livechat/01-department.js +++ /dev/null @@ -1,154 +0,0 @@ -import { expect } from 'chai'; - -import { getCredentials, api, request, credentials } from '../../../data/api-data.js'; -import { updatePermission, updateSetting } from '../../../data/permissions.helper'; -import { createDepartment } from '../../../data/livechat/department.js'; - -describe('LIVECHAT - departments', function () { - this.retries(0); - let department; - - before((done) => getCredentials(done)); - - before((done) => { - updateSetting('Livechat_enabled', true) - .then(() => createDepartment()) - .then((createdDepartment) => { - department = createdDepartment; - done(); - }) - .catch(console.log); - }); - - describe('livechat/department', () => { - it('should return an "unauthorized error" when the user does not have the necessary permission ["view-livechat-departments", "view-l-room"]', (done) => { - updatePermission('view-l-room', []) - .then(() => updatePermission('view-livechat-departments', [])) - .then(() => { - request - .get(api('livechat/department')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('unauthorized'); - }) - .end(done); - }); - }); - it('should return an array of departments', (done) => { - updatePermission('view-l-room', ['admin']) - .then(() => updatePermission('view-livechat-departments', ['admin'])) - .then(() => { - request - .get(api('livechat/department')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body.departments).to.be.an('array'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('count'); - }) - .end(done); - }); - }); - it('should return an array of departments even requested with count and offset params', (done) => { - updatePermission('view-l-room', ['admin']) - .then(() => updatePermission('view-livechat-departments', ['admin'])) - .then(() => { - request - .get(api('livechat/department')) - .set(credentials) - .query({ - count: 5, - offset: 0, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body.departments).to.be.an('array'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('count'); - }) - .end(done); - }); - }); - }); - - describe('livechat/department/id', () => { - it('should return an "unauthorized error" when the user does not have the necessary permission ["view-livechat-departments", "view-l-room"]', (done) => { - updatePermission('view-l-room', []) - .then(() => updatePermission('view-livechat-departments', [])) - .then(() => { - request - .get(api(`livechat/department/${department._id}`)) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('error-not-authorized'); - }) - .end(done); - }); - }); - it('should return the created department without the agents if the user does not have the necessary permission', (done) => { - updatePermission('view-l-room', ['admin']) - .then(() => updatePermission('view-livechat-departments', [])) - .then(() => { - request - .get(api(`livechat/department/${department._id}`)) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('department'); - expect(res.body).to.not.have.property('agents'); - expect(res.body.department._id).to.be.equal(department._id); - }) - .end(done); - }); - }); - it('should return the created department without the agents if the user does have the permission but request to no include the agents', (done) => { - updatePermission('view-livechat-departments', ['admin']).then(() => { - request - .get(api(`livechat/department/${department._id}?includeAgents=false`)) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('department'); - expect(res.body).to.not.have.property('agents'); - expect(res.body.department._id).to.be.equal(department._id); - }) - .end(done); - }); - }); - it('should return the created department', (done) => { - updatePermission('view-l-room', ['admin']) - .then(() => updatePermission('view-livechat-departments', ['admin'])) - .then(() => { - request - .get(api(`livechat/department/${department._id}`)) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('department'); - expect(res.body).to.have.property('agents'); - expect(res.body.department._id).to.be.equal(department._id); - }) - .end(done); - }); - }); - }); -}); diff --git a/apps/meteor/tests/end-to-end/api/livechat/agents.js b/apps/meteor/tests/end-to-end/api/livechat/agents.js index 3d010564951d..cd3244e4673e 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/agents.js +++ b/apps/meteor/tests/end-to-end/api/livechat/agents.js @@ -24,10 +24,12 @@ describe('LIVECHAT - Agents', function () { }); }); + // TODO: missing test cases for POST method describe('livechat/users/:type', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { - updatePermission('view-l-room', []) + updatePermission('edit-omnichannel-contact', []) .then(() => updatePermission('transfer-livechat-guest', [])) + .then(() => updatePermission('manage-livechat-agents', [])) .then(() => { request .get(api('livechat/users/agent')) @@ -58,7 +60,7 @@ describe('LIVECHAT - Agents', function () { }); }); it('should return an array of agents', (done) => { - updatePermission('view-l-room', ['admin']) + updatePermission('edit-omnichannel-contact', ['admin']) .then(() => updatePermission('transfer-livechat-guest', ['admin'])) .then(() => { request @@ -149,3 +151,9 @@ describe('LIVECHAT - Agents', function () { }); }); }); + +// TODO: +// Missing tests for following endpoint: +// livechat/users/:type/:_id +// livechat/agent.info/:rid/:token +// livechat/agent.next/:token diff --git a/apps/meteor/tests/end-to-end/api/livechat/dashboards.js b/apps/meteor/tests/end-to-end/api/livechat/dashboards.js index dc0b9c9fd1c3..b95803f967d1 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/dashboards.js +++ b/apps/meteor/tests/end-to-end/api/livechat/dashboards.js @@ -16,6 +16,7 @@ describe('LIVECHAT - dashboards', function () { const expectedMetrics = [ 'Total_conversations', 'Open_conversations', + 'On_Hold_conversations', 'Total_messages', 'Busiest_time', 'Total_abandoned_chats', diff --git a/apps/meteor/tests/end-to-end/api/livechat/integrations.js b/apps/meteor/tests/end-to-end/api/livechat/integrations.js index f5afb5f973e1..131ab937a6a1 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/integrations.js +++ b/apps/meteor/tests/end-to-end/api/livechat/integrations.js @@ -8,7 +8,9 @@ describe('LIVECHAT - Integrations', function () { before((done) => getCredentials(done)); - before((done) => updateSetting('Livechat_enabled', true).then(done)); + before((done) => { + updateSetting('Livechat_enabled', true).then(done); + }); describe('livechat/integrations.settings', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/visitors.ts b/apps/meteor/tests/end-to-end/api/livechat/visitors.ts index 7bebb2a2de78..3d90e6890bb3 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/visitors.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/visitors.ts @@ -1,9 +1,10 @@ import { expect } from 'chai'; -import type { ILivechatVisitor } from '@rocket.chat/core-typings'; +import type { ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../../data/api-data.js'; import { updatePermission, updateSetting } from '../../../data/permissions.helper'; -import { createVisitor } from '../../../data/livechat/rooms.js'; +import { makeAgentAvailable, createAgent, createLivechatRoom, createVisitor } from '../../../data/livechat/rooms.js'; describe('LIVECHAT - visitors', function () { this.retries(0); @@ -12,12 +13,18 @@ describe('LIVECHAT - visitors', function () { before((done) => getCredentials(done)); before((done) => { - updateSetting('Livechat_enabled', true) - .then(() => createVisitor()) - .then((createdVisitor) => { - visitor = createdVisitor; - done(); - }); + updateSetting('Livechat_enabled', true).then(() => + updatePermission('view-livechat-manager', ['admin']).then(() => + createAgent().then(() => + makeAgentAvailable().then(() => + createVisitor().then((createdVisitor) => { + visitor = createdVisitor; + done(); + }), + ), + ), + ), + ); }); describe('livechat/visitors.info', () => { @@ -28,7 +35,7 @@ describe('LIVECHAT - visitors', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); expect(res.body.error).to.be.equal('error-not-authorized'); }) @@ -42,7 +49,7 @@ describe('LIVECHAT - visitors', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); expect(res.body.error).to.be.equal('visitor-not-found'); }) @@ -55,7 +62,7 @@ describe('LIVECHAT - visitors', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(200) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', true); expect(res.body.visitor._id).to.be.equal(visitor._id); }) @@ -71,7 +78,7 @@ describe('LIVECHAT - visitors', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); expect(res.body.error).to.be.equal('error-not-authorized'); }) @@ -85,7 +92,7 @@ describe('LIVECHAT - visitors', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -93,19 +100,23 @@ describe('LIVECHAT - visitors', function () { }); it('should return an array of pages', (done) => { updatePermission('view-l-room', ['admin']).then(() => { - request - .get(api('livechat/visitors.pagesVisited/GENERAL')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body.pages).to.be.an('array'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('count'); - }) - .end(done); + createVisitor().then((createdVisitor: ILivechatVisitor) => { + createLivechatRoom(createdVisitor.token).then((createdRoom: IOmnichannelRoom) => { + request + .get(api(`livechat/visitors.pagesVisited/${createdRoom._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body.pages).to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); + }); }); }); }); @@ -118,7 +129,7 @@ describe('LIVECHAT - visitors', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); expect(res.body.error).to.be.equal('error-not-authorized'); }) @@ -132,7 +143,7 @@ describe('LIVECHAT - visitors', function () { .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -140,175 +151,49 @@ describe('LIVECHAT - visitors', function () { }); it('should return an array of chat history', (done) => { updatePermission('view-l-room', ['admin']).then(() => { - request - .get(api(`livechat/visitors.chatHistory/room/GENERAL/visitor/${visitor._id}`)) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body.history).to.be.an('array'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('count'); - }) - .end(done); + createVisitor().then((createdVisitor: ILivechatVisitor) => { + createLivechatRoom(createdVisitor.token).then((createdRoom: IOmnichannelRoom) => { + request + .get(api(`livechat/visitors.chatHistory/room/${createdRoom._id}/visitor/${createdVisitor._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body.history).to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); + }); }); }); }); - describe('livechat/visitor', () => { - it("should return a 'failure error' when scope of the custom field is equal to 'visitor' and livechatDataByToken cannot by updated", (done) => { - request - .get(api('livechat/visitor')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('failure'); - }) - .end(done); - }); - - it('should return an error when the "token" query parameter is not valid', (done) => { - request - .get(api('livechat/visitor?token=invalid')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - - it('should return an error when the "name" query parameter is not valid', (done) => { - request - .get(api('livechat/visitor?name=invalid')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - - it('should return an error when the "email" query parameter is not valid', (done) => { - request - .get(api('livechat/visitor?email=invalid')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - - it('should return an error when the "department" query parameter is not valid', (done) => { - request - .get(api('livechat/visitor?department=invalid')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - - it('should return an error when the "phone" query parameter is not valid', (done) => { - request - .get(api('livechat/visitor?phone=invalid')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - - it('should return an error when the "username" query parameter is not valid', (done) => { - request - .get(api('livechat/visitor?username=invalid')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - - it('should return an error when the "customFields" query parameter is not valid', (done) => { - request - .get(api('livechat/visitor?customFields=invalid')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - - it('should return nothing when there is no customField', (done) => { - request - .get(api(`livechat/visitor?token=123&name=John&email=test@gmail.com&department=test&phone=123456789&customFields={}`)) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.rooms).to.equal(null); - }) - .end(done); - }); - - it('should return a visitor when the query params is all valid', (done) => { - request - .get( - api( - `livechat/visitor?token=123&name=John&email=test@gmail.com&department=test&phone=123456789&customFields={"key": "123", "value": "test", "overwrite": "false"}`, - ), - ) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body.rooms).to.be.an('object'); - expect(res.body).to.have.property('visitor'); - }) - .end(done); - }); - }); - describe('livechat/visitor/:token', () => { // get it("should return a 'invalid token' error when visitor with given token does not exist ", (done) => { request - .get(api('livechat/visitor/:token=invalid')) + .get(api('livechat/visitor/invalid')) .set(credentials) .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { + .expect(400) + .expect((res: Response) => { expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('invalid-token'); + expect(res.body.error).to.be.equal('[invalid-token]'); }) .end(done); }); it('should return an error when the "token" query parameter is not valid', (done) => { request - .get(api('livechat/visitor/:token=invalid')) + .get(api('livechat/visitor/invalid')) .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); @@ -316,13 +201,12 @@ describe('LIVECHAT - visitors', function () { it('should return a visitor when the query params is all valid', (done) => { request - .get(api(`livechat/visitor/:token=123`)) + .get(api(`livechat/visitor/${visitor.token}`)) .set(credentials) .expect('Content-Type', 'application/json') .expect(200) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', true); - expect(res.body.rooms).to.be.an('object'); expect(res.body).to.have.property('visitor'); }) .end(done); @@ -331,69 +215,80 @@ describe('LIVECHAT - visitors', function () { // delete it("should return a 'invalid token' error when visitor with given token does not exist ", (done) => { request - .delete(api('livechat/visitor/:token=invalid')) + .delete(api('livechat/visitor/invalid')) .set(credentials) .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { + .expect(400) + .expect((res: Response) => { expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('invalid-token'); + expect(res.body.error).to.be.equal('[invalid-token]'); }) .end(done); }); it('should return an error when the "token" query parameter is not valid', (done) => { request - .delete(api('livechat/visitor/:token=invalid')) + .delete(api('livechat/visitor/invalid')) .set(credentials) .expect('Content-Type', 'application/json') .expect(400) - .expect((res) => { + .expect((res: Response) => { expect(res.body).to.have.property('success', false); }) .end(done); }); it("should return a 'visitor-has-open-rooms' error when there are open rooms", (done) => { - request - .delete(api('livechat/visitor/:token=123')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('visitor-has-open-rooms'); - }) - .end(done); + createVisitor().then((createdVisitor: ILivechatVisitor) => { + createLivechatRoom(createdVisitor.token).then(() => { + request + .delete(api(`livechat/visitor/${createdVisitor.token}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.be.equal('Cannot remove visitors with opened rooms [visitor-has-open-rooms]'); + }) + .end(done); + }); + }); }); - it("should return a 'error-removing-visitor' error when removeGuest's result is false", (done) => { - request - .delete(api('livechat/visitor/:token=123')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.error).to.be.equal('visitor-has-open-rooms'); - }) - .end(done); + it('should return a visitor when the query params is all valid', (done) => { + createVisitor().then((createdVisitor: ILivechatVisitor) => { + request + .delete(api(`livechat/visitor/${createdVisitor.token}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('visitor'); + expect(res.body.visitor).to.have.property('_id'); + expect(res.body.visitor).to.have.property('ts'); + expect(res.body.visitor._id).to.be.equal(createdVisitor._id); + }) + .end(done); + }); }); - it('should return a visitor when the query params is all valid', (done) => { + it("should return a 'error-removing-visitor' error when removeGuest's result is false", (done) => { request - .delete(api(`livechat/visitor/:token=123`)) + .delete(api('livechat/visitor/123')) .set(credentials) .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body.rooms).to.be.an('object'); - expect(res.body).to.have.property('visitor'); - expect(res.body).to.have.property('visitor').that.includes('_id'); - expect(res.body).to.have.property('visitor').that.includes('ts'); + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); }) .end(done); }); }); }); + +// TODO: Missing tests for the following endpoints: +// - /v1/livechat/visitor.status +// - /v1/livechat/visitor.callStatus +// - /v1/livechat/visitor/:token/room +// - /v1/livechat/visitor diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index eedb05de800b..e50432c76879 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -32,4 +32,6 @@ export interface ILivechatVisitorsModel extends IBaseModel { findOneVisitorByPhone(phone: string): Promise; removeDepartmentById(_id: string): Promise; + + getNextVisitorUsername(): Promise; } diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 8e6799312a83..674acb0b4f35 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -815,6 +815,107 @@ const LivechatPrioritiesPropsSchema = { export const isLivechatPrioritiesProps = ajv.compile(LivechatPrioritiesPropsSchema); +type POSTOmnichannelContactProps = { + _id?: string; + token: string; + name: string; + username?: string; + email?: string; + phone?: string; + customFields?: Record; + contactManager?: { + username: string; + }; +}; + +const POSTOmnichannelContactSchema = { + type: 'object', + properties: { + _id: { + type: 'string', + nullable: true, + }, + token: { + type: 'string', + }, + name: { + type: 'string', + }, + username: { + type: 'string', + }, + email: { + type: 'string', + nullable: true, + }, + phone: { + type: 'string', + nullable: true, + }, + customFields: { + type: 'object', + nullable: true, + }, + contactManager: { + type: 'object', + nullable: true, + properties: { + username: { + type: 'string', + }, + }, + }, + }, + required: ['token', 'name', 'username'], + additionalProperties: false, +}; + +export const isPOSTOmnichannelContactProps = ajv.compile(POSTOmnichannelContactSchema); + +type GETOmnichannelContactProps = { contactId: string }; + +const GETOmnichannelContactSchema = { + type: 'object', + properties: { + contactId: { + type: 'string', + }, + }, + required: ['contactId'], + additionalProperties: false, +}; + +export const isGETOmnichannelContactProps = ajv.compile(GETOmnichannelContactSchema); + +type GETOmnichannelContactSearchProps = { email: string } | { phone: string }; + +const GETOmnichannelContactSearchSchema = { + anyOf: [ + { + type: 'object', + properties: { + email: { + type: 'string', + }, + }, + required: ['email'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + phone: { + type: 'string', + }, + }, + required: ['phone'], + additionalProperties: false, + }, + ], +}; + +export const isGETOmnichannelContactSearchProps = ajv.compile(GETOmnichannelContactSearchSchema); + export type OmnichannelEndpoints = { '/v1/livechat/appearance': { GET: () => { @@ -1038,4 +1139,13 @@ export type OmnichannelEndpoints = { }>, ) => PaginatedResult<{ visitors: any[] }>; }; + 'omnichannel/contact': { + POST: (params: POSTOmnichannelContactProps) => { contact: string }; + + GET: (params: GETOmnichannelContactProps) => { contact: ILivechatVisitor | null }; + }; + + 'omnichannel/contact.search': { + GET: (params: GETOmnichannelContactSearchProps) => { contact: ILivechatVisitor | null }; + }; };