diff --git a/.github/workflows/e2e-linux.yml b/.github/workflows/e2e-linux.yml index eddb79444c..0777003d85 100644 --- a/.github/workflows/e2e-linux.yml +++ b/.github/workflows/e2e-linux.yml @@ -53,12 +53,12 @@ jobs: max_attempts: 3 command: cd packages/e2e-tests && npm run test oneClient.test.ts - - name: Run two clients test + - name: Run multiple clients test uses: nick-fields/retry@v2 with: timeout_minutes: 25 max_attempts: 3 - command: cd packages/e2e-tests && npm run test twoClients.test.ts + command: cd packages/e2e-tests && npm run test multipleClients.test.ts - name: Run invitation link test - Includes 2 separate application clients uses: nick-fields/retry@v2 diff --git a/.github/workflows/e2e-mac.yml b/.github/workflows/e2e-mac.yml index 5fea371bec..6f0483f752 100644 --- a/.github/workflows/e2e-mac.yml +++ b/.github/workflows/e2e-mac.yml @@ -61,11 +61,11 @@ jobs: max_attempts: 3 command: cd packages/e2e-tests && npm run test oneClient.test.ts - - name: Run two clients test + - name: Run multiple clients test uses: nick-fields/retry@v2 with: timeout_minutes: 25 max_attempts: 3 - command: cd packages/e2e-tests && npm run test twoClients.test.ts + command: cd packages/e2e-tests && npm run test multipleClients.test.ts diff --git a/.github/workflows/e2e-win.yml b/.github/workflows/e2e-win.yml index dd4e142d1d..100e13d4f0 100644 --- a/.github/workflows/e2e-win.yml +++ b/.github/workflows/e2e-win.yml @@ -68,11 +68,11 @@ jobs: shell: powershell - name: Kill Quiet - run: Stop-Process -Name "Quiet" -Force + run: Get-Process -Name "Quiet" -ErrorAction SilentlyContinue | Stop-Process -Force shell: powershell - name: Kill tor - run: Stop-Process -Name "tor" -Force + run: Get-Process -Name "tor" -ErrorAction SilentlyContinue | Stop-Process -Force shell: powershell - name: Delay @@ -87,13 +87,13 @@ jobs: shell: bash command: cd packages/e2e-tests && npm run test oneClient.test.ts - - name: Run two clients test + - name: Run multiple clients test uses: nick-fields/retry@v2 with: timeout_minutes: 30 max_attempts: 3 shell: bash - command: cd packages/e2e-tests && npm run test twoClients.test.ts + command: cd packages/e2e-tests && npm run test multipleClients.test.ts - name: Run invitation link test - Includes 2 separate application clients uses: nick-fields/retry@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index aa6ae16af3..47fb3d29d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ * Running Chromatic tests for forked PRs +* Added e2e test for user joining community when owner is offline. Improved e2e tests + * Bump github actions/* to versions using node16 * Project can now be bootstraped on Windows (powershell) diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.ts index 865a2ebb35..f96b6d88a0 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts @@ -361,7 +361,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI targetPort: this.ports.libp2pHiddenService, peers, } - this.logger('libp2p params', params) await this.libp2pService.createInstance(params) // KACPER diff --git a/packages/backend/src/nest/socket/socket.service.ts b/packages/backend/src/nest/socket/socket.service.ts index 9690a53f46..5449c1d35b 100644 --- a/packages/backend/src/nest/socket/socket.service.ts +++ b/packages/backend/src/nest/socket/socket.service.ts @@ -172,9 +172,6 @@ export class SocketService extends EventEmitter implements OnModuleInit { ownerCertificate: payload.certificate, rootCa: payload.permsData.certificate, } - - console.log('Metadata from state-manager', communityMetadataPayload) - this.emit(SocketActionTypes.SEND_COMMUNITY_METADATA, communityMetadataPayload) }) diff --git a/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.test.tsx b/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.test.tsx index 5ea510e3df..d55f830c5e 100644 --- a/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.test.tsx +++ b/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.test.tsx @@ -36,6 +36,7 @@ describe('BasicMessage', () => { >
{ >
{ >
{ >
@@ -219,7 +220,7 @@ export const BasicMessageComponent: React.FC {userLabel && !infoMessage && ( - + { >
(resolve => setTimeout(() => resolve(), 2000)) } + this.isOpened = false + console.log('App closed', this.buildSetup.dataDir) } get saveStateButton() { @@ -59,12 +68,6 @@ export class StartingLoadingPanel { get element() { return this.driver.wait(until.elementLocated(By.xpath('//div[@data-testid="startingPanelComponent"]'))) } - // get element() { - // return this.driver.wait(until.elementLocated(By.xpath(`//span[text()="${this.text}"]`))) - // } - // get title() { - // return this.driver.findElement(By.xpath(`//span[text()="${this.text}"]`)) - // } } export class WarningModal { @@ -227,6 +230,19 @@ export class Channel { return await messagesGroup.findElement(By.xpath('//p[@data-testid="/messagesGroupContent-/"]')) } + async waitForUserMessage(username: string, messageContent: string) { + console.log(`Waiting for user "${username}" message "${messageContent}"`) + return this.driver.wait(async () => { + const messages = await this.getUserMessages(username) + const hasMessage = messages.find(async msg => { + const messageText = await msg.getText() + console.log(`got message "${messageText}"`) + return messageText.includes(messageContent) + }) + return hasMessage + }) + } + get getAllMessages() { return this.driver.wait(until.elementsLocated(By.xpath('//*[contains(@data-testid, "userMessages-")]'))) } @@ -256,6 +272,26 @@ export class Channel { ) } + async waitForLabel(username: string, label: string) { + console.log(`Waiting for user's "${username}" label "${label}" label`) + await this.driver.wait(async () => { + const labels = await this.driver.findElements(By.xpath(`//*[contains(@data-testid, "userLabel-${username}")]`)) + const properLabels = labels.filter(async labelElement => { + const labelText = await labelElement.getText() + return labelText === label + }) + return properLabels.length > 0 + }) + } + + async waitForLabelsNotPresent(username: string) { + console.log(`Waiting for user's "${username}" label to not be present`) + await this.driver.wait(async () => { + const labels = await this.driver.findElements(By.xpath(`//*[contains(@data-testid, "userLabel-${username}")]`)) + return labels.length === 0 + }) + } + async getMessage(text: string) { return await this.driver.wait(until.elementLocated(By.xpath(`//span[contains(text(),"${text}")]`))) } @@ -375,18 +411,27 @@ export class DebugModeModal { } get element() { - return this.driver.wait(until.elementLocated(By.xpath("//h3[text()='App is running in debug mode']"))) + return this.driver.wait(until.elementLocated(By.xpath("//h3[text()='App is running in debug mode']")), 5000) } get button() { - return this.driver.wait(until.elementLocated(By.xpath("//button[text()='Understand']"))) + return this.driver.wait(until.elementLocated(By.xpath("//button[text()='Understand']")), 5000) } async close() { - console.log('Closing debug modal') - await this.element.isDisplayed() - const button = await this.button - console.log('Debug modal title is displayed') + if (!process.env.TEST_MODE) return + let button + try { + console.log('Closing debug modal') + await this.element.isDisplayed() + console.log('Debug modal title is displayed') + button = await this.button + console.log('Debug modal button is displayed') + } catch (e) { + console.log('Debug modal might have been covered by "join community" modal', e.message) + return + } + await button.isDisplayed() console.log('Button is displayed') await button.click() diff --git a/packages/e2e-tests/src/tests/invitationLink.test.ts b/packages/e2e-tests/src/tests/invitationLink.test.ts index 0b4e917019..e43f845071 100644 --- a/packages/e2e-tests/src/tests/invitationLink.test.ts +++ b/packages/e2e-tests/src/tests/invitationLink.test.ts @@ -44,14 +44,6 @@ describe('New user joins using invitation link while having app opened', () => { await ownerApp.open() }) - if (process.env.TEST_MODE) { - it('Owner closes debug modal', async () => { - console.log('Invitation Link', 2) - const debugModal = new DebugModeModal(ownerApp.driver) - await debugModal.close() - }) - } - it('JoinCommunityModal - owner switches to create community', async () => { console.log('Invitation Link', 4) const joinModal = new JoinCommunityModal(ownerApp.driver) @@ -121,13 +113,6 @@ describe('New user joins using invitation link while having app opened', () => { console.log('Guest opens app') await guestApp.open() }) - if (process.env.TEST_MODE) { - it('Close debug modal', async () => { - console.log('Invitation Link', 12) - const debugModal = new DebugModeModal(guestApp.driver) - await debugModal.close() - }) - } it.skip('Guest clicks invitation link with invalid invitation code', async () => { // Fix when modals ordering is fixed (joining modal hiddes warning modal) diff --git a/packages/e2e-tests/src/tests/multipleClients.test.ts b/packages/e2e-tests/src/tests/multipleClients.test.ts new file mode 100644 index 0000000000..9adf41a272 --- /dev/null +++ b/packages/e2e-tests/src/tests/multipleClients.test.ts @@ -0,0 +1,438 @@ +import { + App, + Channel, + ChannelContextMenu, + CreateCommunityModal, + DebugModeModal, + JoinCommunityModal, + RegisterUsernameModal, + Sidebar, +} from '../selectors' +import logger from '../logger' +const log = logger('ManyClients') + +interface UserTestData { + username: string + app: App + messages: string[] +} + +jest.setTimeout(900000) +describe('Multiple Clients', () => { + let generalChannelOwner: Channel + let generalChannelUser1: Channel + let generalChannelUser3: Channel + + let secondChannelUser1: Channel + + let channelContextMenuOwner: ChannelContextMenu + + let invitationCode: string + + let sidebarOwner: Sidebar + let sidebarUser1: Sidebar + + let users: Record + + const communityName = 'testcommunity' + const displayedCommunityName = 'Testcommunity' + const newChannelName = 'mid-night-club' + + const sleep = async (time = 1000) => { + await new Promise(resolve => + setTimeout(() => { + resolve() + }, time) + ) + } + + beforeAll(async () => { + const commonApp = new App() + users = { + owner: { + username: 'owner', + messages: ['Hi', 'Hello', 'After guest left the app'], + app: new App(), + }, + user1: { + username: 'user-joining-1', + messages: ['Nice to meet you all'], + app: commonApp, + }, + user2: { + username: 'user-joining-1-1', + messages: ['Nice to meet you again'], + app: commonApp, + }, + user3: { + username: 'user-joining-2', + messages: ['Hi everyone'], + app: new App(), + }, + } + }) + + afterAll(async () => { + for (const user of Object.values(users)) { + await user.app.close() + } + }) + + beforeEach(async () => { + await sleep(1000) + }) + + describe('Stages:', () => { + it('Owner opens the app', async () => { + await users.owner.app.open() + }) + + it('Owner sees "join community" modal and switches to "create community" modal', async () => { + const joinModal = new JoinCommunityModal(users.owner.app.driver) + const isJoinModal = await joinModal.element.isDisplayed() + expect(isJoinModal).toBeTruthy() + await joinModal.switchToCreateCommunity() + }) + it('Owner submits valid community name', async () => { + const createModal = new CreateCommunityModal(users.owner.app.driver) + const isCreateModal = await createModal.element.isDisplayed() + expect(isCreateModal).toBeTruthy() + await createModal.typeCommunityName(communityName) + await createModal.submit() + }) + it('Owner sees "register username" modal and submits valid username', async () => { + const registerModal = new RegisterUsernameModal(users.owner.app.driver) + const isRegisterModal = await registerModal.element.isDisplayed() + expect(isRegisterModal).toBeTruthy() + await registerModal.typeUsername(users.owner.username) + await registerModal.submit() + }) + it('Owner registers successfully and sees general channel', async () => { + generalChannelOwner = new Channel(users.owner.app.driver, 'general') + const isGeneralChannel = await generalChannelOwner.element.isDisplayed() + const generalChannelText = await generalChannelOwner.element.getText() + expect(isGeneralChannel).toBeTruthy() + expect(generalChannelText).toEqual('# general') + }) + it('Owner sends a message', async () => { + const isMessageInput = await generalChannelOwner.messageInput.isDisplayed() + expect(isMessageInput).toBeTruthy() + await generalChannelOwner.sendMessage(users.owner.messages[0]) + }) + it("Owner's message is visible on channel", async () => { + const messages = await generalChannelOwner.getUserMessages(users.owner.username) + const text = await messages[1].getText() + expect(text).toEqual(users.owner.messages[0]) + }) + it('Owner opens the settings tab and gets an invitation code', async () => { + const settingsModal = await new Sidebar(users.owner.app.driver).openSettings() + const isSettingsModal = await settingsModal.element.isDisplayed() + expect(isSettingsModal).toBeTruthy() + await sleep(2000) + await settingsModal.switchTab('invite') // TODO: Fix - the invite tab should be default for the owner + await sleep(2000) + const invitationCodeElement = await settingsModal.invitationCode() + await sleep(2000) + invitationCode = await invitationCodeElement.getText() + await sleep(2000) + console.log({ invitationCode }) + expect(invitationCode).not.toBeUndefined() + log('Received invitation code:', invitationCode) + await settingsModal.close() + }) + + it('First user opens the app', async () => { + console.log('Second client') + await users.user1.app.open() + }) + + it('First user submits invitation code received from owner', async () => { + console.log('new user - 3') + const joinCommunityModal = new JoinCommunityModal(users.user1.app.driver) + const isJoinCommunityModal = await joinCommunityModal.element.isDisplayed() + expect(isJoinCommunityModal).toBeTruthy() + console.log({ invitationCode }) + await joinCommunityModal.typeCommunityCode(invitationCode) + await joinCommunityModal.submit() + }) + + it('First user submits valid username', async () => { + console.log('new user - 5') + const registerModal = new RegisterUsernameModal(users.user1.app.driver) + const isRegisterModal = await registerModal.element.isDisplayed() + expect(isRegisterModal).toBeTruthy() + await registerModal.clearInput() + await registerModal.typeUsername(users.user1.username) + await registerModal.submit() + }) + + it('First user joins successfully sees general channel and sends a message', async () => { + console.log('new user - 7') + generalChannelUser1 = new Channel(users.user1.app.driver, 'general') + await generalChannelUser1.element.isDisplayed() + const isMessageInput2 = await generalChannelUser1.messageInput.isDisplayed() + expect(isMessageInput2).toBeTruthy() + console.log('FETCHING CHANNEL MESSAGES!') + await new Promise(resolve => + setTimeout(() => { + resolve() + }, 15000) + ) + await generalChannelUser1.sendMessage(users.user1.messages[0]) + }) + it("First user's sent message is visible in a channel", async () => { + const messages2 = await generalChannelUser1.getUserMessages(users.user1.username) + const messages1 = await generalChannelUser1.getUserMessages(users.owner.username) + console.log({ messages1, messages2 }) + const text2 = await messages2[0].getText() + expect(text2).toEqual(users.user1.messages[0]) + }) + it('First user opens the settings tab and copies updated invitation code', async () => { + const settingsModal = await new Sidebar(users.user1.app.driver).openSettings() + const isSettingsModal = await settingsModal.element.isDisplayed() + expect(isSettingsModal).toBeTruthy() + await sleep(2000) + await settingsModal.switchTab('invite') + await sleep(2000) + const invitationCodeElement = await settingsModal.invitationCode() + await sleep(2000) + invitationCode = await invitationCodeElement.getText() + await sleep(2000) + console.log(`${invitationCode} copied from non owner`) + expect(invitationCode).not.toBeUndefined() + await settingsModal.close() + }) + + it('Owner goes offline', async () => { + await users.owner.app.close() + }) + + it('Second user opens the app', async () => { + console.log('Third client') + await users.user3.app.open() + const debugModal = new DebugModeModal(users.user3.app.driver) + await debugModal.close() + }) + + it('Second user starts to join when owner is offline', async () => { + const joinCommunityModal = new JoinCommunityModal(users.user3.app.driver) + const isJoinCommunityModal = await joinCommunityModal.element.isDisplayed() + expect(isJoinCommunityModal).toBeTruthy() + console.log({ invitationCode }) + await joinCommunityModal.typeCommunityCode(invitationCode) + await joinCommunityModal.submit() + }) + + it('Second user submits valid, not-duplicated username', async () => { + console.log('nereeew user - 5') + const registerModal = new RegisterUsernameModal(users.user3.app.driver) + const isRegisterModal = await registerModal.element.isDisplayed() + expect(isRegisterModal).toBeTruthy() + await registerModal.clearInput() + await registerModal.typeUsername(users.user3.username) + await registerModal.submit() + }) + + it('Second user sees general channel', async () => { + console.log('new user - 7') + generalChannelUser3 = new Channel(users.user3.app.driver, 'general') + await generalChannelUser3.element.isDisplayed() + const isMessageInput = await generalChannelUser3.messageInput.isDisplayed() + expect(isMessageInput).toBeTruthy() + }) + + it('Second user can send a message, they see their message tagged as "unregistered"', async () => { + console.log('Second guest FETCHING CHANNEL MESSAGES!') + await new Promise(resolve => + setTimeout(() => { + resolve() + }, 15000) + ) + await generalChannelUser3.sendMessage(users.user3.messages[0]) + generalChannelUser3 = new Channel(users.user3.app.driver, 'general') + await generalChannelUser3.waitForLabel(users.user3.username, 'Unregistered') + }) + + it('First user sees that unregistered user\'s messages are marked as "unregistered"', async () => { + await generalChannelUser1.waitForLabel(users.user3.username, 'Unregistered') + }) + + it('Owner goes back online', async () => { + await users.owner.app.open() + const debugModal = new DebugModeModal(users.owner.app.driver) + await debugModal.close() + }) + + it('Second user receives certificate, they can see confirmation that they registered', async () => { + await generalChannelUser3.waitForUserMessage( + users.owner.username, + `@${users.user3.username} has joined ${displayedCommunityName}!` + ) + }) + + it('"Unregistered" label is removed from second user\'s messages', async () => { + generalChannelOwner = new Channel(users.owner.app.driver, 'general') + await generalChannelOwner.waitForLabelsNotPresent(users.user3.username) + }) + + it('Channel creation - Owner creates second channel', async () => { + sidebarOwner = new Sidebar(users.owner.app.driver) + await sidebarOwner.addNewChannel(newChannelName) + await sidebarOwner.switchChannel(newChannelName) + const channels = await sidebarOwner.getChannelList() + expect(channels.length).toEqual(2) + }) + it('Channel creation - Owner sends message in second channel', async () => { + const newChannel = new Channel(users.owner.app.driver, newChannelName) + const isMessageInput = await newChannel.messageInput.isDisplayed() + expect(isMessageInput).toBeTruthy() + await newChannel.sendMessage(users.owner.messages[1]) + }) + it('Channel creation - User reads message in second channel', async () => { + sidebarUser1 = new Sidebar(users.user1.app.driver) + await sidebarUser1.switchChannel(newChannelName) + secondChannelUser1 = new Channel(users.user1.app.driver, newChannelName) + await new Promise(resolve => + setTimeout(() => { + resolve() + }, 2000) + ) + await secondChannelUser1.waitForUserMessage(users.owner.username, users.owner.messages[1]) + }) + it('Channel deletion - Owner deletes second channel', async () => { + channelContextMenuOwner = new ChannelContextMenu(users.owner.app.driver) + await channelContextMenuOwner.openMenu() + await channelContextMenuOwner.openDeletionChannelModal() + await channelContextMenuOwner.deleteChannel() + const channels = await sidebarOwner.getChannelList() + expect(channels.length).toEqual(1) + }) + it('Channel deletion - User sees info about channel deletion in general channel', async () => { + await sleep(5000) + await generalChannelUser1.waitForUserMessage( + users.owner.username, + `@${users.owner.username} deleted #${newChannelName}` + ) + }) + it('Channel deletion - User can create channel with the same name and is fresh channel', async () => { + await sidebarUser1.addNewChannel(newChannelName) + await sidebarUser1.switchChannel(newChannelName) + const messages = await secondChannelUser1.getUserMessages(users.user1.username) + expect(messages.length).toEqual(1) + await new Promise(resolve => + setTimeout(() => { + resolve() + }, 2000) + ) + const channels = await sidebarOwner.getChannelList() + expect(channels.length).toEqual(2) + }) + // End of tests for Windows + if (process.platform !== 'win32') { + it('Leave community', async () => { + console.log('TEST 2') + const settingsModal = await new Sidebar(users.user1.app.driver).openSettings() + const isSettingsModal = await settingsModal.element.isDisplayed() + expect(isSettingsModal).toBeTruthy() + await settingsModal.openLeaveCommunityModal() + await settingsModal.leaveCommunityButton() + }) + // Delete general channel while guest is absent + it('Channel deletion - Owner recreates general channel', async () => { + console.log('TEST 3') + await new Promise(resolve => setTimeout(() => resolve(), 10000)) + const isGeneralChannel = await generalChannelOwner.messageInput.isDisplayed() + expect(isGeneralChannel).toBeTruthy() + await channelContextMenuOwner.openMenu() + await channelContextMenuOwner.openDeletionChannelModal() + await channelContextMenuOwner.deleteChannel() + const channels = await sidebarOwner.getChannelList() + expect(channels.length).toEqual(2) + }) + + it('Leave community - Guest re-join to community successfully', async () => { + console.log('TEST 4') + const debugModal = new DebugModeModal(users.user1.app.driver) + await debugModal.close() + const joinCommunityModal = new JoinCommunityModal(users.user1.app.driver) + const isJoinCommunityModal = await joinCommunityModal.element.isDisplayed() + expect(isJoinCommunityModal).toBeTruthy() + await joinCommunityModal.typeCommunityCode(invitationCode) + await joinCommunityModal.submit() + }) + it('Leave community - Guest registers new username', async () => { + console.log('TEST 5') + const registerModal2 = new RegisterUsernameModal(users.user1.app.driver) + const isRegisterModal2 = await registerModal2.element.isDisplayed() + expect(isRegisterModal2).toBeTruthy() + await registerModal2.typeUsername(users.user2.username) + await registerModal2.submit() + }) + + // Check correct channels replication + it('Channel deletion - User sees information about recreation general channel and see correct amount of messages', async () => { + console.log('TEST 6') + generalChannelUser1 = new Channel(users.user1.app.driver, 'general') + await generalChannelUser1.element.isDisplayed() + await new Promise(resolve => + setTimeout(() => { + resolve() + }, 10000) + ) + + await generalChannelUser1.waitForUserMessage( + users.owner.username, + `@${users.owner.username} deleted all messages in #general` + ) + await generalChannelUser1.waitForUserMessage( + users.owner.username, + `@${users.user2.username} has joined Testcommunity! 🎉` + ) + }) + + it('Leave community - Guest sends a message after rejoining community as a new user', async () => { + console.log('TEST 7') + generalChannelUser1 = new Channel(users.user1.app.driver, 'general') + await generalChannelUser1.element.isDisplayed() + const isMessageInput2 = await generalChannelUser1.messageInput.isDisplayed() + expect(isMessageInput2).toBeTruthy() + await new Promise(resolve => + setTimeout(() => { + resolve() + }, 5000) + ) + await generalChannelUser1.sendMessage(users.user2.messages[0]) + }) + it('Leave community - Sent message is visible in a channel', async () => { + console.log('TEST 8') + await generalChannelUser1.waitForUserMessage(users.user2.username, users.user2.messages[0]) + }) + it('Owner closes app', async () => { + await users.owner.app.close({ forceSaveState: true }) + await new Promise(resolve => setTimeout(() => resolve(), 20000)) + }) + + it('Guest closes app', async () => { + console.log('TEST 9') + await users.user1.app?.close() + }) + + it('Owner re-opens app', async () => { + await users.owner.app?.open() + await new Promise(resolve => setTimeout(() => resolve(), 10000)) + }) + + it('Guest closes app - Owner sends another message after guest left the app', async () => { + console.log('TEST 10') + generalChannelOwner = new Channel(users.owner.app.driver, 'general') + const isMessageInput = await generalChannelOwner.messageInput.isDisplayed() + expect(isMessageInput).toBeTruthy() + await generalChannelOwner.sendMessage(users.owner.messages[2]) + }) + it('Guest closes app - Check if message is visible for owner', async () => { + console.log('TEST 11') + await generalChannelOwner.waitForUserMessage(users.owner.username, users.owner.messages[2]) + }) + } + }) +}) diff --git a/packages/e2e-tests/src/tests/oneClient.test.ts b/packages/e2e-tests/src/tests/oneClient.test.ts index ce774dcc4e..090f268708 100644 --- a/packages/e2e-tests/src/tests/oneClient.test.ts +++ b/packages/e2e-tests/src/tests/oneClient.test.ts @@ -26,13 +26,6 @@ describe('One Client', () => { await app.close() }) describe('User opens app for the first time', () => { - if (process.env.TEST_MODE) { - it('Close debug modal', async () => { - const debugModal = new DebugModeModal(app.driver) - await debugModal.close() - }) - } - it('Get opened app process data', () => { const processData = app.buildSetup.getProcessData() dataDirPath = processData.dataDirPath @@ -110,13 +103,6 @@ describe('One Client', () => { await app.open() }) - if (process.env.TEST_MODE) { - it('Close debug modal', async () => { - const debugModal = new DebugModeModal(app.driver) - await debugModal.close() - }) - } - it('User sees "general channel" page', async () => { const generalChannel = new Channel(app.driver, 'general') const isGeneralChannel = await generalChannel.element.isDisplayed() diff --git a/packages/e2e-tests/src/tests/twoClients.test.ts b/packages/e2e-tests/src/tests/twoClients.test.ts deleted file mode 100644 index 463f585ef3..0000000000 --- a/packages/e2e-tests/src/tests/twoClients.test.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { - App, - Channel, - ChannelContextMenu, - CreateCommunityModal, - DebugModeModal, - JoinCommunityModal, - JoiningLoadingPanel, - RegisterUsernameModal, - Sidebar, -} from '../selectors' -import logger from '../logger' -const log = logger('Two Clients:') - -jest.setTimeout(900000) -describe('Two Clients', () => { - let ownerApp: App - let guestApp: App - - let registerModal2: RegisterUsernameModal - - let generalChannel: Channel - let generalChannel2: Channel - - let secondChannel: Channel - let secondChannel2: Channel - - let channelContextMenu: ChannelContextMenu - - let invitationCode: string - - let sidebar: Sidebar - let sidebar2: Sidebar - - const communityName = 'testcommunity' - const ownerUsername = 'bob' - const ownerMessages = ['Hi', 'Hello', 'After guest leave app'] - const joiningUserUsername = 'alice-joining' - const joiningUserUsername2 = 'alice2' - const joiningUserMessages = ['Nice to meet you all', 'Nice to meet you again'] - const newChannelName = 'mid-night-club' - - const sleep = async (time = 1000) => - await new Promise(resolve => - setTimeout(() => { - resolve() - }, time) - ) - - beforeAll(async () => { - ownerApp = new App() - }) - - afterAll(async () => { - await ownerApp?.close() - }) - - beforeEach(async () => { - await sleep(1000) - }) - - describe('Stages:', () => { - it('Owner opens the app', async () => { - await ownerApp.open() - }) - - if (process.env.TEST_MODE) { - it('Close debug modal', async () => { - const debugModal = new DebugModeModal(ownerApp.driver) - await debugModal.close() - }) - } - - it('JoinCommunityModal - owner switch to create community', async () => { - const joinModal = new JoinCommunityModal(ownerApp.driver) - const isJoinModal = await joinModal.element.isDisplayed() - expect(isJoinModal).toBeTruthy() - await joinModal.switchToCreateCommunity() - }) - it('CreateCommunityModal - owner create his community', async () => { - const createModal = new CreateCommunityModal(ownerApp.driver) - const isCreateModal = await createModal.element.isDisplayed() - expect(isCreateModal).toBeTruthy() - await createModal.typeCommunityName(communityName) - await createModal.submit() - }) - it('RegisterUsernameModal - owner has registered', async () => { - const registerModal = new RegisterUsernameModal(ownerApp.driver) - const isRegisterModal = await registerModal.element.isDisplayed() - expect(isRegisterModal).toBeTruthy() - await registerModal.typeUsername(ownerUsername) - await registerModal.submit() - }) - it('Connecting to peers modal', async () => { - const loadingPanelCommunity = new JoiningLoadingPanel(ownerApp.driver) - const isLoadingPanelCommunity = await loadingPanelCommunity.element.isDisplayed() - expect(isLoadingPanelCommunity).toBeTruthy() - }) - it('General channel check', async () => { - generalChannel = new Channel(ownerApp.driver, 'general') - const isGeneralChannel = await generalChannel.element.isDisplayed() - const generalChannelText = await generalChannel.element.getText() - expect(isGeneralChannel).toBeTruthy() - expect(generalChannelText).toEqual('# general') - }) - it('Send message', async () => { - const isMessageInput = await generalChannel.messageInput.isDisplayed() - expect(isMessageInput).toBeTruthy() - await generalChannel.sendMessage(ownerMessages[0]) - }) - it('Visible message', async () => { - const messages = await generalChannel.getUserMessages(ownerUsername) - const text = await messages[1].getText() - expect(text).toEqual(ownerMessages[0]) - }) - it('Opens the settings tab and gets an invitation code', async () => { - const settingsModal = await new Sidebar(ownerApp.driver).openSettings() - const isSettingsModal = await settingsModal.element.isDisplayed() - expect(isSettingsModal).toBeTruthy() - await sleep(2000) - await settingsModal.switchTab('invite') // TODO: Fix - the invite tab should be default for the owner - await sleep(2000) - const invitationCodeElement = await settingsModal.invitationCode() - await sleep(2000) - invitationCode = await invitationCodeElement.getText() - await sleep(2000) - console.log({ invitationCode }) - expect(invitationCode).not.toBeUndefined() - log('Received invitation code:', invitationCode) - await settingsModal.close() - }) - - it('Guest setup', async () => { - console.log('Second client') - guestApp = new App() - await guestApp.open() - }) - if (process.env.TEST_MODE) { - it('Close debug modal', async () => { - const debugModal = new DebugModeModal(guestApp.driver) - await debugModal.close() - }) - } - - it('Guest joins the new community successfully', async () => { - console.log('new user - 3') - const joinCommunityModal = new JoinCommunityModal(guestApp.driver) - const isJoinCommunityModal = await joinCommunityModal.element.isDisplayed() - expect(isJoinCommunityModal).toBeTruthy() - console.log({ invitationCode }) - await joinCommunityModal.typeCommunityCode(invitationCode) - await joinCommunityModal.submit() - }) - - it.skip('RegisterUsernameModal - User tries to register already taken username, sees error', async () => { - console.log('new user - 4') - registerModal2 = new RegisterUsernameModal(guestApp.driver) - const isRegisterModal2 = await registerModal2.element.isDisplayed() - expect(isRegisterModal2).toBeTruthy() - await registerModal2.typeUsername(ownerUsername) - await registerModal2.submit() - const usernameTakenError = await registerModal2.error.isDisplayed() - expect(usernameTakenError).toBeTruthy() - }) - - it('RegisterUsernameModal - User successfully register not taken username', async () => { - console.log('new user - 5') - registerModal2 = new RegisterUsernameModal(guestApp.driver) - const isRegisterModal = await registerModal2.element.isDisplayed() - expect(isRegisterModal).toBeTruthy() - await registerModal2.clearInput() - await registerModal2.typeUsername(joiningUserUsername) - await registerModal2.submit() - }) - - it.skip('JoiningLoadingPanel', async () => { - console.log('new user - 6') - const loadingPanelCommunity2 = new JoiningLoadingPanel(ownerApp.driver) - const isLoadingPanelCommunity2 = await loadingPanelCommunity2.element.isDisplayed() - expect(isLoadingPanelCommunity2).toBeTruthy() - }) - it('User sends a message', async () => { - console.log('new user - 7') - generalChannel2 = new Channel(guestApp.driver, 'general') - await generalChannel2.element.isDisplayed() - const isMessageInput2 = await generalChannel2.messageInput.isDisplayed() - expect(isMessageInput2).toBeTruthy() - console.log('FETCHING CHANNEL MESSAGES!') - await new Promise(resolve => - setTimeout(() => { - resolve() - }, 15000) - ) - await generalChannel2.sendMessage(joiningUserMessages[0]) - }) - it('Sent message is visible in a channel', async () => { - console.log('new user - 8') - const messages2 = await generalChannel2.getUserMessages(joiningUserUsername) - const messages1 = await generalChannel2.getUserMessages(ownerUsername) - - console.log({ messages1, messages2 }) - const text2 = await messages2[0].getText() - expect(text2).toEqual(joiningUserMessages[0]) - }) - - it('Channel creation - Owner create second channel', async () => { - sidebar = new Sidebar(ownerApp.driver) - await sidebar.addNewChannel(newChannelName) - await sidebar.switchChannel(newChannelName) - const channels = await sidebar.getChannelList() - expect(channels.length).toEqual(2) - }) - it('Channel creation - Owner send message in second channel', async () => { - secondChannel = new Channel(ownerApp.driver, newChannelName) - const isMessageInput = await secondChannel.messageInput.isDisplayed() - expect(isMessageInput).toBeTruthy() - await secondChannel.sendMessage(ownerMessages[1]) - }) - it('Channel creation - User read message in second channel', async () => { - sidebar2 = new Sidebar(guestApp.driver) - await sidebar2.switchChannel(newChannelName) - secondChannel2 = new Channel(guestApp.driver, newChannelName) - await new Promise(resolve => - setTimeout(() => { - resolve() - }, 2000) - ) - const messages = await secondChannel2.getUserMessages(ownerUsername) - const text = await messages[1].getText() - expect(text).toEqual(ownerMessages[1]) - }) - it('Channel deletion - Owner delete second channel', async () => { - channelContextMenu = new ChannelContextMenu(ownerApp.driver) - await channelContextMenu.openMenu() - await channelContextMenu.openDeletionChannelModal() - await channelContextMenu.deleteChannel() - const channels = await sidebar.getChannelList() - expect(channels.length).toEqual(1) - }) - it('Channel deletion - User see info about channel deletion in general channel', async () => { - await sleep(5000) - const messages = await generalChannel2.getUserMessages(ownerUsername) - const text = await messages[3].getText() - expect(text).toEqual(`@${ownerUsername} deleted #${newChannelName}`) - }) - it('Channel deletion - User can create channel with the same name and is fresh channel', async () => { - console.log('TEST 1') - await sidebar2.addNewChannel(newChannelName) - await sidebar2.switchChannel(newChannelName) - const messages = await secondChannel2.getUserMessages(joiningUserUsername) - expect(messages.length).toEqual(1) - await new Promise(resolve => - setTimeout(() => { - resolve() - }, 2000) - ) - const channels = await sidebar.getChannelList() - expect(channels.length).toEqual(2) - }) - // End of tests for Windows - if (process.platform !== 'win32') { - it('Leave community', async () => { - console.log('TEST 2') - const settingsModal = await new Sidebar(guestApp.driver).openSettings() - const isSettingsModal = await settingsModal.element.isDisplayed() - expect(isSettingsModal).toBeTruthy() - await settingsModal.openLeaveCommunityModal() - await settingsModal.leaveCommunityButton() - }) - if (process.env.TEST_MODE) { - it('Leave community - Close debug modal', async () => { - const debugModal = new DebugModeModal(guestApp.driver) - await debugModal.close() - }) - } - // Delete general channel while guest is absent - it('Channel deletion - Owner recreate general channel', async () => { - console.log('TEST 3') - await new Promise(resolve => setTimeout(() => resolve(), 10000)) - const isGeneralChannel = await generalChannel.messageInput.isDisplayed() - expect(isGeneralChannel).toBeTruthy() - await channelContextMenu.openMenu() - await channelContextMenu.openDeletionChannelModal() - await channelContextMenu.deleteChannel() - const channels = await sidebar.getChannelList() - expect(channels.length).toEqual(2) - }) - - it('Leave community - Guest re-join to community successfully', async () => { - console.log('TEST 4') - const joinCommunityModal = new JoinCommunityModal(guestApp.driver) - const isJoinCommunityModal = await joinCommunityModal.element.isDisplayed() - expect(isJoinCommunityModal).toBeTruthy() - await joinCommunityModal.typeCommunityCode(invitationCode) - await joinCommunityModal.submit() - }) - it('Leave community - Guest register new username', async () => { - console.log('TEST 5') - const registerModal2 = new RegisterUsernameModal(guestApp.driver) - const isRegisterModal2 = await registerModal2.element.isDisplayed() - expect(isRegisterModal2).toBeTruthy() - await registerModal2.typeUsername(joiningUserUsername2) - await registerModal2.submit() - }) - - // Check correct channels replication - it('Channel deletion - User see information about recreation general channel and see correct amount of messages', async () => { - console.log('TEST 6') - generalChannel2 = new Channel(guestApp.driver, 'general') - await generalChannel2.element.isDisplayed() - await new Promise(resolve => - setTimeout(() => { - resolve() - }, 10000) - ) - const messages = await generalChannel2.getUserMessages(ownerUsername) - const text1 = await messages[0].getText() - const text2 = await messages[1].getText() - expect(messages.length).toEqual(2) - expect(text1).toEqual(`@${ownerUsername} deleted all messages in #general`) - expect(text2).toEqual(`@${joiningUserUsername2} has joined Testcommunity! 🎉`) - }) - - it('Leave community - Guest sends a message', async () => { - console.log('TEST 7') - generalChannel2 = new Channel(guestApp.driver, 'general') - await generalChannel2.element.isDisplayed() - const isMessageInput2 = await generalChannel2.messageInput.isDisplayed() - expect(isMessageInput2).toBeTruthy() - await new Promise(resolve => - setTimeout(() => { - resolve() - }, 5000) - ) - await generalChannel2.sendMessage(joiningUserMessages[1]) - }) - it('Leave community - Sent message is visible in a channel', async () => { - console.log('TEST 8') - const messages2 = await generalChannel2.getUserMessages(joiningUserUsername2) - const text2 = await messages2[0].getText() - expect(text2).toEqual(joiningUserMessages[1]) - }) - it('Owner close app', async () => { - await ownerApp.close({ forceSaveState: true }) - await new Promise(resolve => setTimeout(() => resolve(), 20000)) - }) - - it('Guest close app', async () => { - console.log('TEST 9') - await guestApp?.close() - }) - - it('Owner re-open app', async () => { - await ownerApp?.open() - await new Promise(resolve => setTimeout(() => resolve(), 10000)) - }) - - if (process.env.TEST_MODE) { - it('Close debug modal', async () => { - const debugModal = new DebugModeModal(ownerApp.driver) - await debugModal.close() - }) - } - - it('Guest close app - Owner send another message after guest leave app', async () => { - console.log('TEST 10') - generalChannel = new Channel(ownerApp.driver, 'general') - const isMessageInput = await generalChannel.messageInput.isDisplayed() - expect(isMessageInput).toBeTruthy() - await generalChannel.sendMessage(ownerMessages[2]) - }) - it('Guest close app - Check if message is visible for owner', async () => { - console.log('TEST 11') - const messages = await generalChannel.getUserMessages(ownerUsername) - const text = await messages[messages.length - 1].getText() - expect(text).toEqual(ownerMessages[2]) - }) - } - }) -}) diff --git a/packages/e2e-tests/src/utils.ts b/packages/e2e-tests/src/utils.ts index 70ba424d44..7379e81199 100644 --- a/packages/e2e-tests/src/utils.ts +++ b/packages/e2e-tests/src/utils.ts @@ -1,5 +1,5 @@ import { Browser, Builder, type ThenableWebDriver } from 'selenium-webdriver' -import { spawn, exec, type ChildProcessWithoutNullStreams, execSync } from 'child_process' +import { spawn, exec, execSync, type ChildProcessWithoutNullStreams } from 'child_process' import { type SupportedPlatformDesktop } from '@quiet/types' import getPort from 'get-port' import path from 'path' @@ -15,8 +15,6 @@ export interface BuildSetupInit { export class BuildSetup { private driver?: ThenableWebDriver | null - public containerId?: string - public ipAddress?: string public port?: number public debugPort?: number public dataDir?: string @@ -104,16 +102,16 @@ export class BuildSetup { await this.initPorts() const env = { DATA_DIR: this.dataDir || 'Quiet', - DEBUG: 'backend*', + DEBUG: 'backend*,desktop*', } if (process.platform === 'win32') { console.log('!WINDOWS!') - this.child = spawn(`cd node_modules/.bin & chromedriver.cmd --port=${this.port}`, [], { + this.child = spawn(`cd node_modules/.bin & chromedriver.cmd --port=${this.port} --verbose`, [], { shell: true, env: Object.assign(process.env, env), }) } else { - this.child = spawn(`node_modules/.bin/chromedriver --port=${this.port}`, [], { + this.child = spawn(`node_modules/.bin/chromedriver --port=${this.port} --verbose`, [], { shell: true, detached: false, env: Object.assign(process.env, env), @@ -127,7 +125,7 @@ export class BuildSetup { ) this.child.on('error', () => { - console.log('ERROR') + console.error('ERROR') this.killNine() }) @@ -145,7 +143,7 @@ export class BuildSetup { console.log('message', data) }) this.child.on('error', data => { - console.log('error', data) + console.error('error', data) }) this.child.stdout.on('data', data => { @@ -153,11 +151,17 @@ export class BuildSetup { }) this.child.stderr.on('data', data => { - console.error(`stderr: ${data}`) + // Quiet logs (handled by 'debug' package) are available in stderr and only with 'verbose' flag on chromedriver + const trashLogs = ['DevTools', 'COMMAND', 'INFO:CONSOLE', '[INFO]:', 'libnotify-WARNING', 'ALSA lib'] + const dataString = `${data}` + for (const l of trashLogs) { + if (dataString.includes(l)) return + } + console.log(dataString) }) this.child.stdin.on('data', data => { - console.error(`stdin: ${data}`) + console.log(`stdin: ${data}`) }) } @@ -196,7 +200,7 @@ export class BuildSetup { } } if (this.driver == null || this.driver === undefined) { - throw new Error('elo') + throw new Error('No driver') } return this.driver diff --git a/packages/state-manager/src/index.ts b/packages/state-manager/src/index.ts index fc41960a33..697ae67bc1 100644 --- a/packages/state-manager/src/index.ts +++ b/packages/state-manager/src/index.ts @@ -82,8 +82,6 @@ export * from './constants' export { formatBytes } from './utils/functions/formatBytes/formatBytes' -export { sortPeers } from './utils/functions/sortPeers/sortPeers' - export { getInvitationCodes } from './utils/functions/invitationCode/invitationCode' export type { Socket } from './types' diff --git a/packages/state-manager/src/sagas/appConnection/connection.selectors.ts b/packages/state-manager/src/sagas/appConnection/connection.selectors.ts index 876d69b5dc..b0f7606fcf 100644 --- a/packages/state-manager/src/sagas/appConnection/connection.selectors.ts +++ b/packages/state-manager/src/sagas/appConnection/connection.selectors.ts @@ -5,9 +5,9 @@ import { allUsers } from '../users/users.selectors' import { communitiesSelectors } from '../communities/communities.selectors' import { peersStatsAdapter } from './connection.adapter' import { connectedPeers } from '../network/network.selectors' -import { sortPeers } from '../../utils/functions/sortPeers/sortPeers' import { type NetworkStats } from './connection.types' import { type User } from '../users/users.types' +import { sortPeers } from '@quiet/common' const connectionSlice: CreatedSelectors[StoreKeys.Connection] = (state: StoreState) => state[StoreKeys.Connection] diff --git a/packages/state-manager/src/utils/functions/sortPeers/sortPeers.ts b/packages/state-manager/src/utils/functions/sortPeers/sortPeers.ts deleted file mode 100644 index c96c895203..0000000000 --- a/packages/state-manager/src/utils/functions/sortPeers/sortPeers.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { isDefined } from '@quiet/common' -import { type NetworkStats } from '@quiet/types' - -/** -This is the very simple algorithm for evaluating the most wanted peers. -1. It takes the peers stats list that contains statistics for every peer our node was ever connected to. -2. Two sorted arrays are created - one sorted by last seen and other by most uptime shared. -3. Arrays are merged taking one element from list one and one element from the second list. Duplicates are ommited -4. We end up with mix of last seen and most uptime descending array of peers, the it is enchanced to libp2p address. - */ -export const sortPeers = (peersAddresses: string[], stats: NetworkStats[]): string[] => { - const lastSeenSorted = [...stats].sort((a, b) => { - return b.lastSeen - a.lastSeen - }) - const mostUptimeSharedSorted = [...stats].sort((a, b) => { - return b.connectionTime - a.connectionTime - }) - - const mostWantedPeers: NetworkStats[] = [] - - for (let i = 0; i < stats.length; i++) { - const peerOne = lastSeenSorted[i] - const peerTwo = mostUptimeSharedSorted[i] - - if (!mostWantedPeers.includes(peerOne)) { - mostWantedPeers.push(peerOne) - } - - if (!mostWantedPeers.includes(peerTwo)) { - mostWantedPeers.push(peerTwo) - } - } - - const peerList = mostWantedPeers.map(peerId => { - return peersAddresses.find(peerAddress => { - const id = peerAddress.split('/')[7] - if (id === peerId.peerId) { - peersAddresses.splice(peersAddresses.indexOf(peerAddress), 1) - return true - } - }) - }) - - return peerList - .concat(peersAddresses) - .filter(address => address !== null) - .filter(isDefined) -}