diff --git a/package.json b/package.json index 1522166e9..01ba712ad 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,8 @@ "format-full": "prettier --list-different --write \"*.{css,js,json,scss,ts,tsx}\" \"./**/*.{css,js,json,scss,ts,tsx}\"", "integration-test": "npx playwright test", + "start-prod-test": "cross-env NODE_ENV=production NODE_APP_INSTANCE=$MULTI electron .", + "integration-test-snapshots": "npx playwright test -g 'profile picture' --update-snapshots", "test": "mocha -r jsdom-global/register --recursive --exit --timeout 10000 \"./ts/test/**/*_test.js\"", "coverage": "nyc --reporter=html mocha -r jsdom-global/register --recursive --exit --timeout 10000 \"./ts/test/**/*_test.js\"", diff --git a/ts/components/basic/SessionToggle.tsx b/ts/components/basic/SessionToggle.tsx index cf55c2ff2..d0922d7b0 100644 --- a/ts/components/basic/SessionToggle.tsx +++ b/ts/components/basic/SessionToggle.tsx @@ -46,6 +46,7 @@ type Props = { active: boolean; onClick: () => void; confirmationDialogParams?: any | undefined; + dataTestId?: string; }; export const SessionToggle = (props: Props) => { @@ -84,7 +85,12 @@ export const SessionToggle = (props: Props) => { }; return ( - + ); diff --git a/ts/components/basic/Spinner.tsx b/ts/components/basic/Spinner.tsx index b3ad430b3..02e7b79b1 100644 --- a/ts/components/basic/Spinner.tsx +++ b/ts/components/basic/Spinner.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; type Props = { size: 'small' | 'normal'; direction?: string; + dataTestId?: string; }; // Module: Spinner @@ -69,7 +70,7 @@ export const Spinner = (props: Props) => { if (size === 'small') { return ( - + @@ -78,7 +79,7 @@ export const Spinner = (props: Props) => { } return ( - + diff --git a/ts/components/conversation/SessionRecording.tsx b/ts/components/conversation/SessionRecording.tsx index 6e3cc7239..cedc8d2cb 100644 --- a/ts/components/conversation/SessionRecording.tsx +++ b/ts/components/conversation/SessionRecording.tsx @@ -129,6 +129,7 @@ export class SessionRecording extends React.Component { iconSize="medium" iconColor={'var(--danger-color)'} onClick={actionPauseFn} + dataTestId="end-voice-message" /> )} {actionPauseAudio && ( @@ -175,6 +176,7 @@ export class SessionRecording extends React.Component { iconRotation={90} onClick={this.onSendVoiceMessage} margin={'var(--margins-sm)'} + dataTestId="send-message-button" /> )} diff --git a/ts/components/conversation/composition/CompositionButtons.tsx b/ts/components/conversation/composition/CompositionButtons.tsx index 921893ed8..fd797f7ac 100644 --- a/ts/components/conversation/composition/CompositionButtons.tsx +++ b/ts/components/conversation/composition/CompositionButtons.tsx @@ -25,6 +25,7 @@ export const AddStagedAttachmentButton = (props: { onClick: () => void }) => { borderRadius="300px" iconPadding="8px" onClick={props.onClick} + dataTestId="attachments-button" /> ); @@ -41,6 +42,7 @@ export const StartRecordingButton = (props: { onClick: () => void }) => { borderRadius="300px" iconPadding="6px" onClick={props.onClick} + dataTestId="microphone-button" /> ); @@ -59,6 +61,7 @@ export const ToggleEmojiButton = React.forwardRef ); diff --git a/ts/components/settings/SessionSettingListItem.tsx b/ts/components/settings/SessionSettingListItem.tsx index d1dd96d57..c6904126f 100644 --- a/ts/components/settings/SessionSettingListItem.tsx +++ b/ts/components/settings/SessionSettingListItem.tsx @@ -128,6 +128,7 @@ export const SessionToggleWithDescription = (props: { onClickToggle: () => void; confirmationDialogParams?: SessionConfirmDialogProps; childrenDescription?: React.ReactNode; // if set, those elements will be appended next to description field (only used for typing message settings as of now) + dataTestId?: string; }) => { const { title, @@ -136,6 +137,7 @@ export const SessionToggleWithDescription = (props: { onClickToggle, confirmationDialogParams, childrenDescription, + dataTestId, } = props; return ( @@ -149,6 +151,7 @@ export const SessionToggleWithDescription = (props: { active={active} onClick={onClickToggle} confirmationDialogParams={confirmationDialogParams} + dataTestId={dataTestId} /> ); diff --git a/ts/components/settings/section/CategoryPermissions.tsx b/ts/components/settings/section/CategoryPermissions.tsx index b17b48bbc..499349a79 100644 --- a/ts/components/settings/section/CategoryPermissions.tsx +++ b/ts/components/settings/section/CategoryPermissions.tsx @@ -64,6 +64,7 @@ export const SettingsCategoryPermissions = (props: { hasPassword: boolean | null title={window.i18n('mediaPermissionsTitle')} description={window.i18n('mediaPermissionsDescription')} active={Boolean(window.getSettingValue('media-permissions'))} + dataTestId="enable-microphone" /> { @@ -73,6 +74,7 @@ export const SettingsCategoryPermissions = (props: { hasPassword: boolean | null title={window.i18n('callMediaPermissionsTitle')} description={window.i18n('callMediaPermissionsDescription')} active={Boolean(window.getCallMediaPermissions())} + data-dataTestId="enable-calls" /> { diff --git a/ts/mains/main_node.ts b/ts/mains/main_node.ts index d4ed3e364..fe7a848ed 100644 --- a/ts/mains/main_node.ts +++ b/ts/mains/main_node.ts @@ -84,7 +84,7 @@ import { installPermissionsHandler } from '../node/permissions'; // checked - on let appStartInitialSpellcheckSetting = true; -const enableTestIntegrationWiderWindow = true; +const enableTestIntegrationWiderWindow = false; const isTestIntegration = enableTestIntegrationWiderWindow && Boolean( diff --git a/ts/test/automation/change_avatar.spec.ts b/ts/test/automation/change_avatar.spec.ts deleted file mode 100644 index 5772f57c3..000000000 --- a/ts/test/automation/change_avatar.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { _electron, expect, Page, test } from '@playwright/test'; -import { openAppAndWait } from './setup/open'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { newUser } from './setup/new_user'; -import { clickOnTestIdWithText, waitForTestIdWithText } from './utilities/utils'; -import { sleepFor } from '../../session/utils/Promise'; - -let window: Page | undefined; -test.beforeEach(beforeAllClean); - -test.afterEach(async () => { - if (window) { - await forceCloseAllWindows([window]); - } -}); - -test('Change profile picture/avatar', async () => { - window = await openAppAndWait('1'); - - await newUser(window, 'userA'); - // Open profile - await clickOnTestIdWithText(window, 'leftpane-primary-avatar'); - // Click on current profile picture - - await waitForTestIdWithText(window, 'copy-button-profile-update', 'Copy'); - - await clickOnTestIdWithText(window, 'image-upload-section'); - await clickOnTestIdWithText(window, 'save-button-profile-update'); - await waitForTestIdWithText(window, 'loading-spinner'); - - await waitForTestIdWithText(window, 'copy-button-profile-update', 'Copy'); - await clickOnTestIdWithText(window, 'modal-close-button'); - - await sleepFor(500); - const leftpaneAvatarContainer = await waitForTestIdWithText(window, 'leftpane-primary-avatar'); - await sleepFor(500); - const screenshot = await leftpaneAvatarContainer.screenshot({ - type: 'jpeg', - // path: 'avatar-updated-blue', - }); - - expect(screenshot).toMatchSnapshot({ name: 'avatar-updated-blue.jpeg' }); -}); diff --git a/ts/test/automation/change_username.spec.ts b/ts/test/automation/change_username.spec.ts deleted file mode 100644 index 73201fda3..000000000 --- a/ts/test/automation/change_username.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { _electron, expect, Page, test } from '@playwright/test'; -import { newUser } from './setup/new_user'; -import { openAppAndWait } from './setup/open'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { clickOnTestIdWithText, typeIntoInput } from './utilities/utils'; -let window: Page | undefined; - -test.beforeEach(beforeAllClean); - -test.afterEach(async () => { - if (window) { - await forceCloseAllWindows([window]); - } -}); - -test('Change username', async () => { - // Open App - window = await openAppAndWait('1'); - // Create user - await newUser(window, 'userA'); - // Open Profile - await clickOnTestIdWithText(window, 'leftpane-primary-avatar'); - // Click on current username to open edit field - await clickOnTestIdWithText(window, 'edit-profile-icon'); - // Type in new username - await typeIntoInput(window, 'profile-name-input', 'new username'); - // await window.fill('.profile-name-input', 'new username'); - // Press enter to confirm username input - await window.keyboard.press('Enter'); - // Wait for Copy button to appear to verify username change - await window.isVisible("'Copy'"); - // verify name change - expect(await window.innerText('[data-testid=your-profile-name]')).toBe('new username'); - // Exit profile module - await window.click('.session-icon-button.small'); -}); diff --git a/ts/test/automation/create_user.spec.ts b/ts/test/automation/create_user.spec.ts index 74663c99f..453a56430 100644 --- a/ts/test/automation/create_user.spec.ts +++ b/ts/test/automation/create_user.spec.ts @@ -1,8 +1,8 @@ -import { _electron, Page, test } from '@playwright/test'; +import { Page, test } from '@playwright/test'; +import { sleepFor } from '../../session/utils/Promise'; +import { beforeAllClean } from './setup/beforeEach'; import { newUser } from './setup/new_user'; import { openAppAndWait } from './setup/open'; -import { sleepFor } from '../../session/utils/Promise'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; import { clickOnMatchingText, clickOnTestIdWithText, diff --git a/ts/test/automation/delete_account.spec.ts b/ts/test/automation/delete_account.spec.ts index 8ab4a2b1e..34feaa031 100644 --- a/ts/test/automation/delete_account.spec.ts +++ b/ts/test/automation/delete_account.spec.ts @@ -1,23 +1,19 @@ -import { _electron, Page, test } from '@playwright/test'; +import { _electron, test } from '@playwright/test'; import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { openAppsAndNewUsers, openAppsNoNewUsers } from './setup/new_user'; +import { newUser } from './setup/new_user'; import { sendNewMessage } from './utilities/send_message'; import { clickOnMatchingText, clickOnTestIdWithText, typeIntoInput } from './utilities/utils'; import { sleepFor } from '../../session/utils/Promise'; +import { openApp } from './setup/open'; // tslint:disable: no-console -let windows: Array = []; test.beforeEach(beforeAllClean); -test.afterEach(() => forceCloseAllWindows(windows)); - test('Delete account from swarm', async () => { - const testMessage = `A -> B: ${Date.now()}`; - const testReply = `B -> A: ${Date.now()}`; - const windowLoggedIn = await openAppsAndNewUsers(2); - windows = windowLoggedIn.windows; - const [windowA, windowB] = windows; - const [userA, userB] = windowLoggedIn.users; + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `${userA.userName} to ${userB.userName}`; + const testReply = `${userB.userName} to ${userA.userName}`; // Create contact and send new message await Promise.all([ sendNewMessage(windowA, userB.sessionid, testMessage), @@ -37,7 +33,7 @@ test('Delete account from swarm', async () => { // Wait for window to close and reopen await sleepFor(10000, true); // await windowA.close(); - const restoringWindows = await openAppsNoNewUsers(1); + const restoringWindows = await openApp(1); const [restoringWindow] = restoringWindows; // Sign in with deleted account and check that nothing restores await clickOnTestIdWithText(restoringWindow, 'restore-using-recovery', 'Restore your account'); diff --git a/ts/test/automation/disappearing_messages.spec.ts b/ts/test/automation/disappearing_messages.spec.ts index 0417b0a81..4044c44b4 100644 --- a/ts/test/automation/disappearing_messages.spec.ts +++ b/ts/test/automation/disappearing_messages.spec.ts @@ -1,21 +1,21 @@ -import { _electron, Page, test } from '@playwright/test'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { messageSent } from './utilities/message'; -import { openAppsAndNewUsers } from './setup/new_user'; +import { test } from '@playwright/test'; +import { sleepFor } from '../../session/utils/Promise'; +import { beforeAllClean } from './setup/beforeEach'; +import { newUser } from './setup/new_user'; +import { openApp } from './setup/open'; +import { sendMessage } from './utilities/message'; import { sendNewMessage } from './utilities/send_message'; import { clickOnMatchingText, clickOnTestIdWithText, + waitForControlMessageWithText, waitForMatchingText, - waitForReadableMessageWithText, waitForTestIdWithText, } from './utilities/utils'; -import { sleepFor } from '../../session/utils/Promise'; -let windows: Array = []; test.beforeEach(beforeAllClean); -test.afterEach(() => forceCloseAllWindows(windows)); +// test.afterEach(() => forceCloseAllWindows(windows)); // tslint:disable: no-console const testMessage = 'Test-Message- (A -> B) '; @@ -23,18 +23,15 @@ const testReply = 'Reply-Test-Message- (B -> A)'; const sentMessage = `${testMessage}${Date.now()}`; const sentReplyMessage = `${testReply} :${Date.now()}`; -test('Disappearing Messages', async () => { +test('Disappearing messages', async () => { // Open App // Create User - const windowLoggedIn = await openAppsAndNewUsers(2); - windows = windowLoggedIn.windows; - const users = windowLoggedIn.users; - const [windowA, windowB] = windows; - const [userA, userB] = users; + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); // Create Contact await sendNewMessage(windowA, userB.sessionid, sentMessage); await sendNewMessage(windowB, userA.sessionid, sentReplyMessage); - await waitForReadableMessageWithText(windowA, 'Your message request has been accepted'); + await waitForControlMessageWithText(windowA, 'Your message request has been accepted'); // await waitForMatchingText(windowA, `You have accepted ${userA.userName}'s message request`); // await waitForMatchingText(windowB, 'Your message request has been accepted'); // Click on user's avatar to open conversation options @@ -48,7 +45,7 @@ test('Disappearing Messages', async () => { // Check config message await waitForTestIdWithText( windowA, - 'readable-message', + 'control-message', 'You set the disappearing message timer to 5 seconds' ); await sleepFor(2000); @@ -57,7 +54,7 @@ test('Disappearing Messages', async () => { await waitForTestIdWithText(windowA, 'disappearing-messages-indicator', '5 seconds'); // Send message // Wait for tick of confirmation - await messageSent(windowA, sentMessage); + await sendMessage(windowA, sentMessage); // Check timer is functioning // Verify message is deleted @@ -82,7 +79,7 @@ test('Disappearing Messages', async () => { // Click chevron to close menu await clickOnTestIdWithText(windowA, 'back-button-conversation-options'); // Check config message - await waitForTestIdWithText(windowA, 'readable-message', 'You disabled disappearing messages.'); + await waitForTestIdWithText(windowA, 'control-message', 'You disabled disappearing messages.'); // Verify message is deleted in windowB for receiver user // Check config message in windowB await waitForMatchingText( diff --git a/ts/test/automation/fixtures/test-file.pdf b/ts/test/automation/fixtures/test-file.pdf new file mode 100644 index 000000000..82b84599a Binary files /dev/null and b/ts/test/automation/fixtures/test-file.pdf differ diff --git a/ts/test/automation/fixtures/test-gif.gif b/ts/test/automation/fixtures/test-gif.gif new file mode 100644 index 000000000..f2f50fa79 Binary files /dev/null and b/ts/test/automation/fixtures/test-gif.gif differ diff --git a/ts/test/automation/fixtures/test-image.png b/ts/test/automation/fixtures/test-image.png new file mode 100644 index 000000000..79b0dab07 Binary files /dev/null and b/ts/test/automation/fixtures/test-image.png differ diff --git a/ts/test/automation/fixtures/test-video.mp4 b/ts/test/automation/fixtures/test-video.mp4 new file mode 100644 index 000000000..ed139d6d5 Binary files /dev/null and b/ts/test/automation/fixtures/test-video.mp4 differ diff --git a/ts/test/automation/group_creation.spec.ts b/ts/test/automation/group_creation.spec.ts deleted file mode 100644 index 78ea89774..000000000 --- a/ts/test/automation/group_creation.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { _electron, Page, test } from '@playwright/test'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { messageSent } from './utilities/message'; -import { openAppsAndNewUsers } from './setup/new_user'; -import { sendNewMessage } from './utilities/send_message'; -import { - clickOnMatchingText, - clickOnTestIdWithText, - typeIntoInput, - waitForReadableMessageWithText, - waitForTestIdWithText, -} from './utilities/utils'; - -const testGroupName = 'Test Group Name'; - -let windows: Array = []; -test.beforeEach(beforeAllClean); - -test.afterEach(() => forceCloseAllWindows(windows)); - -test('Create group', async () => { - const windowLoggedIn = await openAppsAndNewUsers(3); - windows = windowLoggedIn.windows; - const users = windowLoggedIn.users; - const [windowA, windowB, windowC] = windows; - const [userA, userB, userC] = users; - // Add contacts - await sendNewMessage(windowA, userC.sessionid, `A -> C: ${Date.now()}`); - await Promise.all([ - sendNewMessage(windowA, userB.sessionid, `A -> B: ${Date.now()}`), - sendNewMessage(windowB, userA.sessionid, `B -> A: ${Date.now()}`), - sendNewMessage(windowC, userA.sessionid, `C -> A: ${Date.now()}`), - ]); - // Click new closed group tab - - await clickOnTestIdWithText(windowA, 'new-conversation-button'); - await clickOnTestIdWithText(windowA, 'chooser-new-group'); // Enter group name - await typeIntoInput(windowA, 'new-closed-group-name', testGroupName); - // Select user B - await clickOnMatchingText(windowA, userB.userName); - // Select user C - await clickOnMatchingText(windowA, userC.userName); - // Click Done - await clickOnTestIdWithText(windowA, 'next-button'); - // Check group was successfully created - await clickOnMatchingText(windowB, testGroupName); - await waitForTestIdWithText(windowB, 'header-conversation-name', testGroupName); - // Send message in group chat from user A - const msgAToGroup = 'A -> Group'; - await messageSent(windowA, msgAToGroup); - // Verify it was received by other two accounts - // Navigate to group in window B - await clickOnTestIdWithText(windowB, 'message-section'); - // Click on test group - await clickOnMatchingText(windowB, testGroupName); - // wait for selector 'test message' in chat window - await waitForReadableMessageWithText(windowB, msgAToGroup); - // Send reply message - const msgBToGroup = 'B -> Group'; - await messageSent(windowB, msgBToGroup); - // Navigate to group in window C - await clickOnTestIdWithText(windowC, 'message-section'); - // Click on test group - await clickOnMatchingText(windowC, testGroupName); - // windowC must see the message from A - await waitForReadableMessageWithText(windowC, msgAToGroup); - // windowC must see the message from B - await waitForReadableMessageWithText(windowC, msgBToGroup); - // Send message from C to the group - const msgCToGroup = 'C -> Group'; - await messageSent(windowC, msgCToGroup); - // windowA should see the message from B and the message from C - await waitForReadableMessageWithText(windowA, msgBToGroup); - await waitForReadableMessageWithText(windowA, msgCToGroup); -}); diff --git a/ts/test/automation/group_testing.spec.ts b/ts/test/automation/group_testing.spec.ts index f75dca10a..7fe03d0e1 100644 --- a/ts/test/automation/group_testing.spec.ts +++ b/ts/test/automation/group_testing.spec.ts @@ -3,13 +3,18 @@ import { beforeAllClean } from './setup/beforeEach'; import { clickOnMatchingText, clickOnTestIdWithText, + typeIntoInput, waitForControlMessageWithText, waitForMatchingText, + waitForTestIdWithText, } from './utilities/utils'; import { renameGroup } from './utilities/rename_group'; import { createGroup } from './setup/create_group'; // import { leaveGroup } from './utilities/leave_group'; -import { newUser, openApp } from './setup/new_user'; +import { newUser } from './setup/new_user'; +import { leaveGroup } from './utilities/leave_group'; +import { openApp } from './setup/open'; +import { sleepFor } from '../../session/utils/Promise'; test.beforeEach(beforeAllClean); @@ -26,18 +31,22 @@ test('Create group', async () => { const testGroupName = 'Tiny Bubble Gang'; await createGroup(testGroupName, userA, windowA, userB, windowB, userC, windowC); // Check config messages in all windows - await waitForControlMessageWithText( - windowA, - `"${userC.userName}", "${userB.userName}", You joined the group.` - ); - await waitForControlMessageWithText( - windowB, - `"${userC.userName}", "${userA.userName}", You joined the group.` - ); - await waitForControlMessageWithText( - windowC, - `"${userB.userName}", "${userA.userName}", You joined the group.` - ); + await sleepFor(1000); + // await waitForTestIdWithText(windowA, 'control-message'); + await Promise.all([ + waitForControlMessageWithText( + windowA, + `"${userB.userName}", "${userC.userName}", You joined the group.` + ), + waitForControlMessageWithText( + windowB, + `You, "${userC.userName}", "${userA.userName}" joined the group.` + ), + waitForControlMessageWithText( + windowC, + `"${userB.userName}", You, "${userA.userName}" joined the group.` + ), + ]); }); test('Change group name', async () => { @@ -56,7 +65,7 @@ test('Change group name', async () => { await renameGroup(windowA, group.userName, newGroupName); // Check config message in window B for group name change await clickOnMatchingText(windowB, newGroupName); - await waitForMatchingText(windowB, `Group name is now ${newGroupName}.`); + await waitForMatchingText(windowB, `Group name is now '${newGroupName}'.`); // Click on conversation options // Check to see that you can't change group name to empty string // Click on edit group name @@ -68,3 +77,52 @@ test('Change group name', async () => { await clickOnMatchingText(windowA, 'Cancel'); await clickOnTestIdWithText(windowA, 'back-button-conversation-options'); }); + +test('Test mentions', async () => { + const [windowA, windowB, windowC] = await openApp(3); + const [userA, userB, userC] = await Promise.all([ + newUser(windowA, 'Alice'), + newUser(windowB, 'Bob'), + newUser(windowC, 'Chloe'), + ]); + const testGroupName = 'Tiny Bubble Gang'; + const group = await createGroup(testGroupName, userA, windowA, userB, windowB, userC, windowC); + + // in windowA we should be able to mentions userB and userC + + await clickOnTestIdWithText(windowA, 'module-conversation__user__profile-name', group.userName); + await typeIntoInput(windowA, 'message-input-text-area', '@'); + // does 'message-input-text-area' have aria-expanded: true when @ is typed into input + await waitForTestIdWithText(windowA, 'mentions-popup-row'); + await waitForTestIdWithText(windowA, 'mentions-popup-row', userB.userName); + await waitForTestIdWithText(windowA, 'mentions-popup-row', userC.userName); + + // in windowB we should be able to mentions userA and userC + await clickOnTestIdWithText(windowB, 'module-conversation__user__profile-name', group.userName); + await typeIntoInput(windowB, 'message-input-text-area', '@'); + // does 'message-input-text-area' have aria-expanded: true when @ is typed into input + await waitForTestIdWithText(windowB, 'mentions-popup-row'); + await waitForTestIdWithText(windowB, 'mentions-popup-row', userA.userName); + await waitForTestIdWithText(windowB, 'mentions-popup-row', userC.userName); + + // in windowC we should be able to mentions userA and userB + await clickOnTestIdWithText(windowC, 'module-conversation__user__profile-name', group.userName); + await typeIntoInput(windowC, 'message-input-text-area', '@'); + // does 'message-input-text-area' have aria-expanded: true when @ is typed into input + await waitForTestIdWithText(windowC, 'mentions-popup-row'); + await waitForTestIdWithText(windowC, 'mentions-popup-row', userA.userName); + await waitForTestIdWithText(windowC, 'mentions-popup-row', userB.userName); +}); + +test('Leave group', async () => { + const [windowA, windowB, windowC] = await openApp(3); + const [userA, userB, userC] = await Promise.all([ + newUser(windowA, 'Alice'), + newUser(windowB, 'Bob'), + newUser(windowC, 'Chloe'), + ]); + const testGroupName = 'Tiny Bubble Gang'; + await createGroup(testGroupName, userA, windowA, userB, windowB, userC, windowC); + + await leaveGroup(windowC); +}); diff --git a/ts/test/automation/group_upkeep.spec.ts b/ts/test/automation/group_upkeep.spec.ts index ec2d9f150..2a77b762a 100644 --- a/ts/test/automation/group_upkeep.spec.ts +++ b/ts/test/automation/group_upkeep.spec.ts @@ -1,16 +1,15 @@ -import { _electron, Page, test } from '@playwright/test'; +import { _electron, test } from '@playwright/test'; import { beforeAllClean } from './setup/beforeEach'; -import { openApp } from './setup/new_user'; import { sendNewMessage } from './utilities/send_message'; import { logIn } from './setup/log_in'; import { userA, userB, userC, userD, userE } from './setup/test_user'; +import { openApp } from './setup/open'; -let windows: Array = []; test.beforeEach(beforeAllClean); test.skip('Group upkeep', async () => { const [windowA, windowB, windowC, windowD, windowE] = await openApp(5); - windows = [windowA, windowB, windowC, windowD, windowE]; + await Promise.all([ logIn(windowA, userA.recoveryPhrase), logIn(windowB, userB.recoveryPhrase), diff --git a/ts/test/automation/linking_device.spec.ts b/ts/test/automation/linked_device_user.spec.ts similarity index 51% rename from ts/test/automation/linking_device.spec.ts rename to ts/test/automation/linked_device_user.spec.ts index 43262b6c6..da3bc8e10 100644 --- a/ts/test/automation/linking_device.spec.ts +++ b/ts/test/automation/linked_device_user.spec.ts @@ -1,5 +1,7 @@ import { _electron, Page, test } from '@playwright/test'; import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; +import { newUser } from './setup/new_user'; +import { openApp } from './setup/open'; import { linkedDevice } from './utilities/linked_device'; import { clickOnTestIdWithText, typeIntoInput, waitForTestIdWithText } from './utilities/utils'; @@ -9,21 +11,22 @@ test.beforeEach(beforeAllClean); test.afterEach(() => forceCloseAllWindows(windows)); // tslint:disable: no-console -test('linking device', async () => { - const { windowA1, windowA2, userA } = await linkedDevice(); - windows.push(windowA1, windowA2); - - await clickOnTestIdWithText(windowA1, 'leftpane-primary-avatar'); +test('Link a device', async () => { + const [windowA] = await openApp(1); + const userA = await newUser(windowA, 'Alice'); + const [windowB] = await linkedDevice(userA.recoveryPhrase); + const newUsername = 'Tiny bubble'; + await clickOnTestIdWithText(windowA, 'leftpane-primary-avatar'); // Verify Username - await waitForTestIdWithText(windowA1, 'your-profile-name', userA.userName); + await waitForTestIdWithText(windowA, 'your-profile-name', userA.userName); // Verify Session ID - await waitForTestIdWithText(windowA1, 'your-session-id', userA.sessionid); + await waitForTestIdWithText(windowA, 'your-session-id', userA.sessionid); // exit profile module - await clickOnTestIdWithText(windowA1, 'modal-close-button'); + await clickOnTestIdWithText(windowA, 'modal-close-button'); // You're almost finished isn't displayed const errorDesc = 'Should not be found'; try { - const elemShouldNotBeFound = windowA2.locator('[data-testid=reveal-recovery-phrase]'); + const elemShouldNotBeFound = windowB.locator('[data-testid=reveal-recovery-phrase]'); if (elemShouldNotBeFound) { console.error('Element not found'); throw new Error(errorDesc); @@ -34,19 +37,18 @@ test('linking device', async () => { throw e; } } - await clickOnTestIdWithText(windowA1, 'leftpane-primary-avatar'); + await clickOnTestIdWithText(windowA, 'leftpane-primary-avatar'); // Click on pencil icon - await clickOnTestIdWithText(windowA1, 'edit-profile-icon'); + await clickOnTestIdWithText(windowA, 'edit-profile-icon'); // Replace old username with new username - const newUsername = 'new-username'; - await typeIntoInput(windowA1, 'profile-name-input', newUsername); + await typeIntoInput(windowA, 'profile-name-input', newUsername); // Press enter to confirm change - await windowA1.keyboard.press('Enter'); + await windowA.keyboard.press('Enter'); // Wait for loading animation // Check username change in window B2 // Click on profile settings in window B - await clickOnTestIdWithText(windowA2, 'leftpane-primary-avatar'); + await clickOnTestIdWithText(windowB, 'leftpane-primary-avatar'); // Verify username has changed to new username - await waitForTestIdWithText(windowA2, 'your-profile-name', newUsername); + await waitForTestIdWithText(windowB, 'your-profile-name', newUsername); // Check message is deleting on both devices }); diff --git a/ts/test/automation/mentions.spec.ts b/ts/test/automation/mentions.spec.ts deleted file mode 100644 index 6d19176c7..000000000 --- a/ts/test/automation/mentions.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { _electron, Page, test } from '@playwright/test'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { clickOnTestIdWithText, typeIntoInput, waitForTestIdWithText } from './utilities/utils'; -import { createGroup } from './setup/create_group'; - -let windows: Array = []; -test.beforeEach(beforeAllClean); - -test.afterEach(() => forceCloseAllWindows(windows)); - -test('Mentions', async () => { - const { userA, userB, userC, windowA, windowB, windowC } = await createGroup('Test Group Name'); - windows = [windowA, windowB, windowC]; - - // in windowA we should be able to mentions userB and userC - - await clickOnTestIdWithText( - windowA, - 'module-conversation__user__profile-name', - 'Test Group Name' - ); - await typeIntoInput(windowA, 'message-input-text-area', '@'); - // does 'message-input-text-area' have aria-expanded: true when @ is typed into input - await waitForTestIdWithText(windowA, 'mentions-popup-row'); - await waitForTestIdWithText(windowA, 'mentions-popup-row', userB.userName); - await waitForTestIdWithText(windowA, 'mentions-popup-row', userC.userName); - - // in windowB we should be able to mentions userA and userC - await clickOnTestIdWithText( - windowB, - 'module-conversation__user__profile-name', - 'Test Group Name' - ); - await typeIntoInput(windowB, 'message-input-text-area', '@'); - // does 'message-input-text-area' have aria-expanded: true when @ is typed into input - await waitForTestIdWithText(windowB, 'mentions-popup-row'); - await waitForTestIdWithText(windowB, 'mentions-popup-row', userA.userName); - await waitForTestIdWithText(windowB, 'mentions-popup-row', userC.userName); - - // in windowC we should be able to mentions userA and userB - await clickOnTestIdWithText( - windowC, - 'module-conversation__user__profile-name', - 'Test Group Name' - ); - await typeIntoInput(windowC, 'message-input-text-area', '@'); - // does 'message-input-text-area' have aria-expanded: true when @ is typed into input - await waitForTestIdWithText(windowC, 'mentions-popup-row'); - await waitForTestIdWithText(windowC, 'mentions-popup-row', userA.userName); - await waitForTestIdWithText(windowC, 'mentions-popup-row', userB.userName); -}); diff --git a/ts/test/automation/message_checks.spec.ts b/ts/test/automation/message_checks.spec.ts new file mode 100644 index 000000000..1f3fa326f --- /dev/null +++ b/ts/test/automation/message_checks.spec.ts @@ -0,0 +1,146 @@ +import { test } from '@playwright/test'; +import { sleepFor } from '../../session/utils/Promise'; +import { beforeAllClean } from './setup/beforeEach'; +import { newUser } from './setup/new_user'; +import { openApp } from './setup/open'; +import { createContact } from './utilities/create_contact'; +import { replyTo } from './utilities/reply_message'; +import { + clickOnMatchingText, + clickOnTestIdWithText, + typeIntoInput, + waitForLoadingAnimationToFinish, +} from './utilities/utils'; + +test.beforeEach(beforeAllClean); + +test('Send image and reply test', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `${userA.userName} sending image to ${userB.userName}`; + const testReply = `${userB.userName} replying to image from ${userA.userName}`; + await createContact(windowA, windowB, userA, userB); + + await windowA.setInputFiles("input[type='file']", 'ts/test/automation/fixtures/test-image.png'); + await typeIntoInput(windowA, 'message-input-text-area', testMessage); + await clickOnTestIdWithText(windowA, 'send-message-button'); + // Click on untrusted attachment in window B + await sleepFor(1000); + await clickOnMatchingText(windowB, 'Click to download media'); + await clickOnTestIdWithText(windowB, 'session-confirm-ok-button'); + await waitForLoadingAnimationToFinish(windowB); + // Waiting for image to change from loading state to loaded (takes a second) + await sleepFor(1000); + + await replyTo(windowB, testMessage, testReply); +}); + +test('Send video and reply test', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `${userA.userName} sending video to ${userB.userName}`; + const testReply = `${userB.userName} replying to video from ${userA.userName}`; + await createContact(windowA, windowB, userA, userB); + + await windowA.setInputFiles("input[type='file']", 'ts/test/automation/fixtures/test-video.mp4'); + await typeIntoInput(windowA, 'message-input-text-area', testMessage); + await sleepFor(100); + await clickOnTestIdWithText(windowA, 'send-message-button'); + await sleepFor(1000); + await clickOnMatchingText(windowB, 'Click to download media'); + await clickOnTestIdWithText(windowB, 'session-confirm-ok-button'); + await waitForLoadingAnimationToFinish(windowB); + // Waiting for videoto change from loading state to loaded (takes a second) + await sleepFor(1000); + await replyTo(windowB, testMessage, testReply); +}); + +test('Send document and reply test', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `${userA.userName} sending document to ${userB.userName}`; + const testReply = `${userB.userName} replying to document from ${userA.userName}`; + await createContact(windowA, windowB, userA, userB); + + await windowA.setInputFiles("input[type='file']", 'ts/test/automation/fixtures/test-file.pdf'); + await typeIntoInput(windowA, 'message-input-text-area', testMessage); + await sleepFor(100); + await clickOnTestIdWithText(windowA, 'send-message-button'); + await sleepFor(1000); + await clickOnMatchingText(windowB, 'Click to download media'); + await clickOnTestIdWithText(windowB, 'session-confirm-ok-button'); + await waitForLoadingAnimationToFinish(windowB); + // Waiting for videoto change from loading state to loaded (takes a second) + await sleepFor(1000); + await replyTo(windowB, testMessage, testReply); +}); + +test('Send voice message and reply test', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + // const testReply = `${userB.userName} to ${userA.userName}`; + await createContact(windowA, windowB, userA, userB); + + await clickOnTestIdWithText(windowA, 'microphone-button'); + await clickOnTestIdWithText(windowA, 'session-toast'); + await clickOnTestIdWithText(windowA, 'enable-microphone'); + await clickOnTestIdWithText(windowA, 'message-section'); + await clickOnTestIdWithText(windowA, 'microphone-button'); + await sleepFor(5000); + await clickOnTestIdWithText(windowA, 'end-voice-message'); + await sleepFor(4000); + await clickOnTestIdWithText(windowA, 'send-message-button'); + await sleepFor(1000); + await clickOnMatchingText(windowB, 'Click to download media'); + await clickOnTestIdWithText(windowB, 'session-confirm-ok-button'); +}); + +test('Send GIF and reply test', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + // const testReply = `${userB.userName} to ${userA.userName}`; + await createContact(windowA, windowB, userA, userB); + + await windowA.setInputFiles("input[type='file']", 'ts/test/automation/fixtures/test-gif.gif'); + await sleepFor(100); + await clickOnTestIdWithText(windowA, 'send-message-button'); + await sleepFor(1000); + await clickOnMatchingText(windowB, 'Click to download media'); +}); + +test('Send long text and reply test', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + + const testReply = `${userB.userName} replying to long text message from ${userA.userName}`; + const longText = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum quis lacinia mi. Praesent fermentum vehicula rhoncus. Aliquam ac purus lobortis, convallis nisi quis, pulvinar elit. Nam commodo eros in molestie lobortis. Donec at mattis est. In tempor ex nec velit mattis, vitae feugiat augue maximus. Nullam risus libero, bibendum et enim et, viverra viverra est. Suspendisse potenti. Sed ut nibh in sem rhoncus suscipit. Etiam tristique leo sit amet ullamcorper dictum. Suspendisse sollicitudin, lectus et suscipit eleifend, libero dui ultricies neque, non elementum nulla orci bibendum lorem. Suspendisse potenti. Aenean a tellus imperdiet, iaculis metus quis, pretium diam. Nunc varius vitae enim vestibulum interdum. In hac habitasse platea dictumst. Donec auctor sem quis eleifend fermentum. Vestibulum neque nulla, maximus non arcu gravida, condimentum euismod turpis. Cras ac mattis orci. Quisque ac enim pharetra felis sodales eleifend. Aliquam erat volutpat. Donec sit amet mollis nibh, eget feugiat ipsum. Integer vestibulum purus ac suscipit egestas. Duis vitae aliquet ligula.'; + + await createContact(windowA, windowB, userA, userB); + + await typeIntoInput(windowA, 'message-input-text-area', longText); + await sleepFor(100); + await clickOnTestIdWithText(windowA, 'send-message-button'); + await sleepFor(1000); + await replyTo(windowB, longText, testReply); +}); + +test('Send link and reply test', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = 'https://nerdlegame.com/'; + const testReply = `${userB.userName} replying to link from ${userA.userName}`; + + await createContact(windowA, windowB, userA, userB); + + await typeIntoInput(windowA, 'message-input-text-area', testMessage); + await sleepFor(5000); + await clickOnTestIdWithText(windowA, 'send-message-button'); + await sleepFor(1000); + await replyTo(windowB, testMessage, testReply); +}); + +// Send link +// Send long text +// Unsend +// Delete message diff --git a/ts/test/automation/message_requests.spec.ts b/ts/test/automation/message_requests.spec.ts index af9d5e083..2cc277b59 100644 --- a/ts/test/automation/message_requests.spec.ts +++ b/ts/test/automation/message_requests.spec.ts @@ -1,29 +1,27 @@ -import { _electron, Page, test } from '@playwright/test'; +import { test } from '@playwright/test'; +import { beforeAllClean } from './setup/beforeEach'; +import { newUser } from './setup/new_user'; +import { openApp } from './setup/open'; +import { sendMessage } from './utilities/message'; import { sendNewMessage } from './utilities/send_message'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { openAppsAndNewUsers } from './setup/new_user'; import { + clickOnMatchingText, clickOnTestIdWithText, waitForMatchingText, waitForTestIdWithText, } from './utilities/utils'; -const testMessage = 'A -> B'; - -let windows: Array = []; test.beforeEach(beforeAllClean); -test.afterEach(() => forceCloseAllWindows(windows)); +// test.afterEach(() => forceCloseAllWindows(windows)); // Open two windows and log into 2 separate accounts test.describe('Message requests', () => { - test('Message request acceptance', async () => { - const windowLoggedIn = await openAppsAndNewUsers(2); - windows = windowLoggedIn.windows; - const users = windowLoggedIn.users; - const [windowA, windowB] = windows; - const [userA, userB] = users; + test('Message requests accept', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `Sender: ${userA.userName} Receiver: ${userB.userName}`; // send a message to User B from User A - await sendNewMessage(windowA, userB.sessionid, `${testMessage}${Date.now()}`); + await sendNewMessage(windowA, userB.sessionid, `${testMessage}`); // Check the message request banner appears and click on it await clickOnTestIdWithText(windowB, 'message-request-banner'); // Select message request from User A @@ -33,19 +31,38 @@ test.describe('Message requests', () => { // Check config message of message request acceptance await waitForTestIdWithText( windowB, - 'readable-message', + 'control-message', `You have accepted ${userA.userName}'s message request` ); await waitForMatchingText(windowB, 'No pending message requests'); }); - test('Message request rejection', async () => { - const windowLoggedIn = await openAppsAndNewUsers(2); - windows = windowLoggedIn.windows; - const users = windowLoggedIn.users; - const [windowA, windowB] = windows; - const [userA, userB] = users; + test('Message requests text reply', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `Sender: ${userA.userName}, Receiver: ${userB.userName}`; + const testReply = `Sender: ${userB.userName}, Receiver: ${userA.userName}`; // send a message to User B from User A - await sendNewMessage(windowA, userB.sessionid, `${testMessage}${Date.now()}`); + await sendNewMessage(windowA, userB.sessionid, `${testMessage}`); + // Check the message request banner appears and click on it + await clickOnTestIdWithText(windowB, 'message-request-banner'); + // Select message request from User A + await clickOnTestIdWithText(windowB, 'module-conversation__user__profile-name', userA.userName); + // Check that using the accept button has intended use + await sendMessage(windowB, testReply); + // Check config message of message request acceptance + await waitForTestIdWithText( + windowB, + 'control-message', + `You have accepted ${userA.userName}'s message request` + ); + await waitForMatchingText(windowB, 'No pending message requests'); + }); + test('Message requests decline', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `Sender: ${userA.userName}, Receiver: ${userB.userName}`; + // send a message to User B from User A + await sendNewMessage(windowA, userB.sessionid, `${testMessage}`); // Check the message request banner appears and click on it await clickOnTestIdWithText(windowB, 'message-request-banner'); // Select message request from User A @@ -58,4 +75,26 @@ test.describe('Message requests', () => { await waitForTestIdWithText(windowB, 'session-toast', 'Blocked'); await waitForMatchingText(windowB, 'No pending message requests'); }); + test('Message requests clear all', async () => { + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `Sender: ${userA.userName}, Receiver: ${userB.userName}`; + // send a message to User B from User A + await sendNewMessage(windowA, userB.sessionid, `${testMessage}`); + // Check the message request banner appears and click on it + await clickOnTestIdWithText(windowB, 'message-request-banner'); + // Select 'Clear All' button + await clickOnMatchingText(windowB, 'Clear All'); + // Confirm decline + await clickOnTestIdWithText(windowB, 'session-confirm-ok-button', 'OK'); + // Navigate back to message request folder to check + await clickOnTestIdWithText(windowB, 'settings-section'); + // Check config message of message request acceptance + await waitForMatchingText(windowB, 'No pending message requests'); + }); }); + +// Clear all requests + +// Delete request (not a feature yet) +// Block request (not a feature yet) diff --git a/ts/test/automation/setup/beforeEach.ts b/ts/test/automation/setup/beforeEach.ts index 6cada0449..aceaaaa00 100644 --- a/ts/test/automation/setup/beforeEach.ts +++ b/ts/test/automation/setup/beforeEach.ts @@ -1,7 +1,8 @@ -import { _electron, Page } from '@playwright/test'; +import { Page } from '@playwright/test'; import { readdirSync, rmdirSync } from 'fs-extra'; -import { dirname, join } from 'path'; -import { MULTI_PREFIX, NODE_ENV, openElectronAppOnly } from './open'; +import { join } from 'path'; +import { isMacOS } from '../../../OS'; +import { MULTI_PREFIX, NODE_ENV } from './open'; // tslint:disable: no-console const getDirectoriesOfSessionDataPath = (source: string) => @@ -12,33 +13,21 @@ const getDirectoriesOfSessionDataPath = (source: string) => }) .filter(n => n.includes(`${NODE_ENV}-${MULTI_PREFIX}`)); -let alreadyCleaned = false; +const alreadyCleaned = false; let alreadyCleanedWaiting = false; -const cleanUpOtherTest = async () => { +function cleanUpOtherTest() { if (alreadyCleaned || alreadyCleanedWaiting) { return; } - alreadyCleaned = true; - const electronApp = await openElectronAppOnly('start'); - - const appPath = await electronApp.evaluate(async ({ app }) => { - return app.getPath('userData'); - }); - const window = await electronApp.firstWindow(); - await window.close(); - if (alreadyCleaned && alreadyCleanedWaiting) { - return; - } alreadyCleanedWaiting = true; - if (!appPath.length) { - throw new Error('appDataPath unset'); + const parentFolderOfAllDataPath = isMacOS() ? '~/Library/Application Support/' : null; + if (!parentFolderOfAllDataPath) { + throw new Error('Only macOS is currrently supported '); } - const parentFolderOfAllDataPath = dirname(appPath); - if (!parentFolderOfAllDataPath || parentFolderOfAllDataPath.length < 20) { throw new Error('parentFolderOfAllDataPath not found or invalid'); } @@ -48,14 +37,11 @@ const cleanUpOtherTest = async () => { console.info('allAppDataPath', allAppDataPath); allAppDataPath.map(folder => { - if (!appPath) { - throw new Error('parentFolderOfAllDataPath unset'); - } const pathToRemove = join(parentFolderOfAllDataPath, folder); rmdirSync(pathToRemove, { recursive: true }); }); console.info('...done'); -}; +} export const beforeAllClean = cleanUpOtherTest; diff --git a/ts/test/automation/setup/create_group.ts b/ts/test/automation/setup/create_group.ts index 3eb9aaf17..6003208ff 100644 --- a/ts/test/automation/setup/create_group.ts +++ b/ts/test/automation/setup/create_group.ts @@ -1,5 +1,5 @@ import { _electron, Page } from '@playwright/test'; -import { messageSent } from '../utilities/message'; +import { sendMessage } from '../utilities/message'; import { sendNewMessage } from '../utilities/send_message'; import { clickOnMatchingText, @@ -56,7 +56,7 @@ export const createGroup = async ( await clickOnMatchingText(windowB, group.userName); await waitForTestIdWithText(windowB, 'header-conversation-name', group.userName); // Send message in group chat from user A - await messageSent(windowA, msgAToGroup); + await sendMessage(windowA, msgAToGroup); // Focus screen await clickOnMatchingText(windowA, msgAToGroup); // Verify it was received by other two accounts @@ -67,7 +67,7 @@ export const createGroup = async ( // wait for selector 'test message' in chat window await waitForControlMessageWithText(windowB, msgAToGroup); // Send reply message - await messageSent(windowB, msgBToGroup); + await sendMessage(windowB, msgBToGroup); // Focus screen // await clickOnTestIdWithText(windowB, 'scroll-to-bottom-button'); await clickOnMatchingText(windowB, msgBToGroup); @@ -79,7 +79,7 @@ export const createGroup = async ( await waitForControlMessageWithText(windowC, msgAToGroup); await waitForControlMessageWithText(windowC, msgBToGroup); // Send message from C to the group - await messageSent(windowC, msgCToGroup); + await sendMessage(windowC, msgCToGroup); // windowA should see the message from B and the message from C await waitForControlMessageWithText(windowA, msgBToGroup); await waitForControlMessageWithText(windowA, msgCToGroup); diff --git a/ts/test/automation/setup/new_user.ts b/ts/test/automation/setup/new_user.ts index e29545da8..026a34cc1 100644 --- a/ts/test/automation/setup/new_user.ts +++ b/ts/test/automation/setup/new_user.ts @@ -1,10 +1,7 @@ -import { _electron, Page } from '@playwright/test'; -import _ from 'lodash'; +import { Page } from '@playwright/test'; import { User } from '../types/testing'; import { clickOnMatchingText, typeIntoInput } from '../utilities/utils'; -import { openAppAndWait } from './open'; -const multisAvailable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - +// tslint:disable: no-console export const newUser = async (window: Page, userName: string): Promise => { // Create User await clickOnMatchingText(window, 'Create Session ID'); @@ -20,46 +17,34 @@ export const newUser = async (window: Page, userName: string): Promise => await clickOnMatchingText(window, 'Reveal Recovery Phrase'); const recoveryPhrase = await window.innerText('[data-testid=recovery-phrase-seed-modal]'); + console.info(`${userName}: Session ID: ${sessionid} and Recovery phrase: ${recoveryPhrase}`); await window.click('.session-icon-button.small'); return { userName, sessionid, recoveryPhrase }; }; -const openAppAndNewUser = async (multi: string): Promise => { - const window = await openAppAndWait(multi); - - const userName = `${multi}-user`; - const loggedIn = await newUser(window, userName); - return { window, ...loggedIn }; -}; +// const openAppAndNewUser = async (multi: string): Promise => { +// const window = await openAppAndWait(multi); -export async function openAppsAndNewUsers(windowToCreate: number) { - if (windowToCreate >= multisAvailable.length) { - throw new Error(`Do you really need ${multisAvailable.length} windows?!`); - } - // if windowToCreate = 3, this array will be ABC. If windowToCreate = 5, this array will be ABCDE - const multisToUse = multisAvailable.slice(0, windowToCreate); - const loggedInDetails = await Promise.all( - [...multisToUse].map(async m => { - return openAppAndNewUser(m); - }) - ); +// const userName = `${multi}-user`; +// const loggedIn = await newUser(window, userName); +// return { window, ...loggedIn }; +// }; - const windows = loggedInDetails.map(w => w.window); - const users = loggedInDetails.map(w => { - return _.pick(w, ['sessionid', 'recoveryPhrase', 'userName']); - }); - return { windows, users }; -} +// export async function openAppsAndNewUsers(windowToCreate: number) { +// if (windowToCreate >= multisAvailable.length) { +// throw new Error(`Do you really need ${multisAvailable.length} windows?!`); +// } +// // if windowToCreate = 3, this array will be ABC. If windowToCreate = 5, this array will be ABCDE +// const multisToUse = multisAvailable.slice(0, windowToCreate); +// const loggedInDetails = await Promise.all( +// [...multisToUse].map(async m => { +// return openAppAndNewUser(m); +// }) +// ); -export async function openApp(windowsToCreate: number) { - if (windowsToCreate >= multisAvailable.length) { - throw new Error(`Do you really need ${multisAvailable.length} windows?!`); - } - // if windowToCreate = 3, this array will be ABC. If windowToCreate = 5, this array will be ABCDE - const multisToUse = multisAvailable.slice(0, windowsToCreate); - return Promise.all( - [...multisToUse].map(async m => { - return openAppAndWait(`${m}`); - }) - ); -} +// const windows = loggedInDetails.map(w => w.window); +// const users = loggedInDetails.map(w => { +// return _.pick(w, ['sessionid', 'recoveryPhrase', 'userName']); +// }); +// return { windows, users }; +// } diff --git a/ts/test/automation/setup/open.ts b/ts/test/automation/setup/open.ts index decbefeb9..756affb89 100644 --- a/ts/test/automation/setup/open.ts +++ b/ts/test/automation/setup/open.ts @@ -4,8 +4,22 @@ import { getAppRootPath } from '../../../node/getRootPath'; export const NODE_ENV = 'production'; export const MULTI_PREFIX = 'test-integration-testnet-'; +const multisAvailable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // tslint:disable: no-console +export async function openApp(windowsToCreate: number) { + if (windowsToCreate >= multisAvailable.length) { + throw new Error(`Do you really need ${multisAvailable.length} windows?!`); + } + // if windowToCreate = 3, this array will be ABC. If windowToCreate = 5, this array will be ABCDE + const multisToUse = multisAvailable.slice(0, windowsToCreate); + return Promise.all( + [...multisToUse].map(async m => { + return openAppAndWait(`${m}`); + }) + ); +} + export const openElectronAppOnly = async (multi: string) => { process.env.NODE_APP_INSTANCE = `${MULTI_PREFIX}-${Date.now()}-${multi}`; process.env.NODE_ENV = NODE_ENV; diff --git a/ts/test/automation/switching_theme.spec.ts b/ts/test/automation/switching_theme.spec.ts index c391d3d2a..6ff483870 100644 --- a/ts/test/automation/switching_theme.spec.ts +++ b/ts/test/automation/switching_theme.spec.ts @@ -1,19 +1,18 @@ -import { _electron, expect, Page, test } from '@playwright/test'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { openAppsAndNewUsers } from './setup/new_user'; +import { expect, test } from '@playwright/test'; +import { beforeAllClean } from './setup/beforeEach'; +import { newUser } from './setup/new_user'; +import { openApp } from './setup/open'; import { clickOnTestIdWithText } from './utilities/utils'; -let windows: Array = []; test.beforeEach(beforeAllClean); -test.afterEach(() => forceCloseAllWindows(windows)); +// test.afterEach(() => forceCloseAllWindows(windows)); test('Switch themes', async () => { // Open App + const [windowA] = await openApp(1); // Create User - const windowLoggedIn = await openAppsAndNewUsers(1); - windows = windowLoggedIn.windows; - const [windowA] = windows; + await newUser(windowA, 'Alice'); // Check light theme colour is correct const darkThemeColor = windowA.locator('.inbox.index'); await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(27, 27, 27)'); diff --git a/ts/test/automation/test.spec.ts b/ts/test/automation/test.spec.ts new file mode 100644 index 000000000..c501c3ee8 --- /dev/null +++ b/ts/test/automation/test.spec.ts @@ -0,0 +1,11 @@ +import { test } from '@playwright/test'; +import { beforeAllClean } from './setup/beforeEach'; +import { openApp } from './setup/open'; +import { clickOnMatchingText } from './utilities/utils'; + +test.beforeEach(beforeAllClean); + +test('Tiny test', async () => { + const [windowA] = await openApp(1); + await clickOnMatchingText(windowA, 'Create Session ID'); +}); diff --git a/ts/test/automation/to do b/ts/test/automation/to do new file mode 100644 index 000000000..8337712ea --- /dev/null +++ b/ts/test/automation/to do @@ -0,0 +1 @@ +// diff --git a/ts/test/automation/types/testing.ts b/ts/test/automation/types/testing.ts index 471f1f627..542fc5fcc 100644 --- a/ts/test/automation/types/testing.ts +++ b/ts/test/automation/types/testing.ts @@ -10,3 +10,5 @@ export type Group = { userTwo: User; userThree: User; }; + +export type Strategy = 'data-testid' | 'class' | ':has-text'; diff --git a/ts/test/automation/unsend_message.spec.ts b/ts/test/automation/unsend_message.spec.ts index e0497ccf8..3459c5c41 100644 --- a/ts/test/automation/unsend_message.spec.ts +++ b/ts/test/automation/unsend_message.spec.ts @@ -1,6 +1,7 @@ -import { _electron, Page, test } from '@playwright/test'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; -import { openAppsAndNewUsers } from './setup/new_user'; +import { test } from '@playwright/test'; +import { beforeAllClean } from './setup/beforeEach'; +import { newUser } from './setup/new_user'; +import { openApp } from './setup/open'; import { sendNewMessage } from './utilities/send_message'; import { clickOnMatchingText, @@ -12,18 +13,14 @@ import { const testMessage = 'A -> B: '; const testReply = 'B -> A: '; -let windows: Array = []; test.beforeEach(beforeAllClean); -test.afterEach(() => forceCloseAllWindows(windows)); +// test.afterEach(() => forceCloseAllWindows(windows)); test('Unsend message', async () => { // Open App - const windowLoggedIn = await openAppsAndNewUsers(2); - windows = windowLoggedIn.windows; - const users = windowLoggedIn.users; - const [windowA, windowB] = windows; - const [userA, userB] = users; + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); // Send message between two users await sendNewMessage(windowA, userB.sessionid, `${testMessage}${Date.now()}`); await sendNewMessage(windowB, userA.sessionid, `${testReply}${Date.now()}`); diff --git a/ts/test/automation/user_actions.spec.ts b/ts/test/automation/user_actions.spec.ts index 7c3a33495..208f5a0b5 100644 --- a/ts/test/automation/user_actions.spec.ts +++ b/ts/test/automation/user_actions.spec.ts @@ -1,27 +1,25 @@ -import { _electron, Page, test } from '@playwright/test'; -import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; - +import { expect, test } from '@playwright/test'; +import { beforeAllClean } from './setup/beforeEach'; +import { sleepFor } from '../../session/utils/Promise'; +import { newUser } from './setup/new_user'; import { sendNewMessage } from './utilities/send_message'; -import { openAppsAndNewUsers } from './setup/new_user'; import { clickOnMatchingText, clickOnTestIdWithText, + typeIntoInput, waitForMatchingText, waitForTestIdWithText, } from './utilities/utils'; +import { openApp } from './setup/open'; -let windows: Array = []; test.beforeEach(beforeAllClean); // test.afterEach(() => forceCloseAllWindows(windows)); // Send message in one to one conversation with new contact test('Create contact', async () => { - const windowLoggedIn = await openAppsAndNewUsers(2); - windows = windowLoggedIn.windows; - const users = windowLoggedIn.users; - const [windowA, windowB] = windows; - const [userA, userB] = users; + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); const testMessage = `${userA.userName} to ${userB.userName}`; const testReply = `${userB.userName} to ${userA.userName}`; @@ -39,13 +37,11 @@ test('Create contact', async () => { await clickOnTestIdWithText(windowA, 'new-conversation-button'); }); -test('Block User', async () => { +test('Block user in conversation options', async () => { // Open app and create user - const windowLoggedIn = await openAppsAndNewUsers(2); - windows = windowLoggedIn.windows; - const users = windowLoggedIn.users; - const [windowA, windowB] = windows; - const [userA, userB] = users; + const [windowA, windowB] = await openApp(2); + const [userA, userB] = await Promise.all([newUser(windowA, 'Alice'), newUser(windowB, 'Bob')]); + const testMessage = `${userA.userName} to ${userB.userName}`; const testReply = `${userB.userName} to ${userA.userName}`; // Create contact and send new message @@ -78,3 +74,51 @@ test('Block User', async () => { await waitForTestIdWithText(windowA, 'session-toast', 'Unblocked'); await waitForMatchingText(windowA, 'No blocked contacts'); }); + +test('Change username', async () => { + // Open App + const [window] = await openApp(1); + // Create user + const newUsername = 'Tiny bubble'; + await newUser(window, 'Alice'); + // Open Profile + await clickOnTestIdWithText(window, 'leftpane-primary-avatar'); + // Click on current username to open edit field + await clickOnTestIdWithText(window, 'edit-profile-icon'); + // Type in new username + await typeIntoInput(window, 'profile-name-input', newUsername); + // await window.fill('.profile-name-input', 'new username'); + // Press enter to confirm username input + await window.keyboard.press('Enter'); + // Wait for Copy button to appear to verify username change + await window.isVisible("'Copy'"); + // verify name change + expect(await window.innerText('[data-testid=your-profile-name]')).toBe(newUsername); + // Exit profile module + await window.click('.session-icon-button.small'); +}); + +test('Change avatar', async () => { + const [window] = await openApp(1); + await newUser(window, 'Alice'); + // Open profile + await clickOnTestIdWithText(window, 'leftpane-primary-avatar'); + // Click on current profile picture + await waitForTestIdWithText(window, 'copy-button-profile-update', 'Copy'); + + await clickOnTestIdWithText(window, 'image-upload-section'); + await clickOnTestIdWithText(window, 'save-button-profile-update'); + await waitForTestIdWithText(window, 'loading-spinner'); + + await waitForTestIdWithText(window, 'copy-button-profile-update', 'Copy'); + await clickOnTestIdWithText(window, 'modal-close-button'); + + await sleepFor(500); + const leftpaneAvatarContainer = await waitForTestIdWithText(window, 'leftpane-primary-avatar'); + await sleepFor(500); + const screenshot = await leftpaneAvatarContainer.screenshot({ + type: 'jpeg', + // path: 'avatar-updated-blue', + }); + expect(screenshot).toMatchSnapshot({ name: 'avatar-updated-blue.jpeg' }); +}); diff --git a/ts/test/automation/user_actions.spec.ts-snapshots/avatar-updated-blue-darwin.jpeg b/ts/test/automation/user_actions.spec.ts-snapshots/avatar-updated-blue-darwin.jpeg new file mode 100644 index 000000000..079323ef7 Binary files /dev/null and b/ts/test/automation/user_actions.spec.ts-snapshots/avatar-updated-blue-darwin.jpeg differ diff --git a/ts/test/automation/utilities/create_contact.ts b/ts/test/automation/utilities/create_contact.ts new file mode 100644 index 000000000..4f46776ea --- /dev/null +++ b/ts/test/automation/utilities/create_contact.ts @@ -0,0 +1,20 @@ +import { Page } from '@playwright/test'; +import { User } from '../types/testing'; +import { sendNewMessage } from './send_message'; +import { clickOnTestIdWithText, waitForTestIdWithText } from './utils'; + +export const createContact = async (windowA: Page, windowB: Page, userA: User, userB: User) => { + const testMessage = `${userA.userName} to ${userB.userName}`; + const testReply = `${userB.userName} to ${userA.userName}`; + // User A sends message to User B + await sendNewMessage(windowA, userB.sessionid, `${testMessage}`); + // User B sends message to User B to USER A + await sendNewMessage(windowB, userA.sessionid, `${testReply}`); + + await clickOnTestIdWithText(windowA, 'new-conversation-button'); + await windowA.waitForTimeout(2000); + await waitForTestIdWithText(windowB, 'module-conversation__user__profile-name', userA.userName); + + // Navigate to contacts tab in User A's window + await clickOnTestIdWithText(windowA, 'new-conversation-button'); +}; diff --git a/ts/test/automation/utilities/leave_group.ts b/ts/test/automation/utilities/leave_group.ts index fec130226..e23f86aeb 100644 --- a/ts/test/automation/utilities/leave_group.ts +++ b/ts/test/automation/utilities/leave_group.ts @@ -9,5 +9,5 @@ export const leaveGroup = async (window: Page) => { // Confirm leave group await clickOnTestIdWithText(window, 'session-confirm-ok-button', 'OK'); // check config message - await waitForTestIdWithText(window, 'readable-message', 'You have left the group.'); + await waitForTestIdWithText(window, 'control-message', 'You have left the group.'); }; diff --git a/ts/test/automation/utilities/linked_device.ts b/ts/test/automation/utilities/linked_device.ts index b38ef0fb0..4af36e8dc 100644 --- a/ts/test/automation/utilities/linked_device.ts +++ b/ts/test/automation/utilities/linked_device.ts @@ -1,15 +1,11 @@ import { _electron } from 'playwright-core'; -import { openAppsAndNewUsers, openAppsNoNewUsers } from '../setup/new_user'; import { logIn } from '../setup/log_in'; +import { openApp } from '../setup/open'; -export async function linkedDevice() { - const windowLoggedIn = await openAppsAndNewUsers(1); - const [windowA1] = windowLoggedIn.windows; - const users = windowLoggedIn.users; - const [userA] = users; - const [windowA2] = await openAppsNoNewUsers(1); +export async function linkedDevice(recoveryPhrase: string) { + const [windowB] = await openApp(1); - await logIn(windowA2, userA.recoveryPhrase); + await logIn(windowB, recoveryPhrase); - return { windowA1, windowA2, userA }; + return [windowB]; } diff --git a/ts/test/automation/utilities/message.ts b/ts/test/automation/utilities/message.ts index 9353677d2..a2282d27a 100644 --- a/ts/test/automation/utilities/message.ts +++ b/ts/test/automation/utilities/message.ts @@ -2,13 +2,13 @@ import { _electron, Page } from '@playwright/test'; import { clickOnTestIdWithText, typeIntoInput } from './utils'; // tslint:disable: no-console -export const messageSent = async (window: Page, message: string) => { +export const sendMessage = async (window: Page, message: string) => { // type into message input box await typeIntoInput(window, 'message-input-text-area', message); // click up arrow (send) await clickOnTestIdWithText(window, 'send-message-button'); // wait for confirmation tick to send reply message - const selc = `css=[data-testid=readable-message]:has-text("${message}"):has([data-testid=msg-status-outgoing][data-testtype=sent])`; + const selc = `css=[data-testid=control-message]:has-text("${message}"):has([data-testid=msg-status-outgoing][data-testtype=sent])`; console.error('waiting for sent tick of message: ', message); const tickMessageSent = await window.waitForSelector(selc, { timeout: 30000 }); diff --git a/ts/test/automation/utilities/reply_message.ts b/ts/test/automation/utilities/reply_message.ts new file mode 100644 index 000000000..a9a949a76 --- /dev/null +++ b/ts/test/automation/utilities/reply_message.ts @@ -0,0 +1,11 @@ +import { Page } from '@playwright/test'; +import { sendMessage } from './message'; +import { clickOnMatchingText, clickOnTestIdWithText, waitForTextMessage } from './utils'; + +export const replyTo = async (window: Page, textMessage: string, replyText: string) => { + await waitForTextMessage(window, textMessage); + await clickOnTestIdWithText(window, 'control-message', textMessage, true); + await clickOnMatchingText(window, 'Reply to message'); + await sendMessage(window, replyText); + console.warn(); +}; diff --git a/ts/test/automation/utilities/send_message.ts b/ts/test/automation/utilities/send_message.ts index 82f15968d..27e818f0f 100644 --- a/ts/test/automation/utilities/send_message.ts +++ b/ts/test/automation/utilities/send_message.ts @@ -1,5 +1,5 @@ import { _electron, Page } from '@playwright/test'; -import { messageSent } from './message'; +import { sendMessage } from './message'; import { clickOnTestIdWithText, typeIntoInput } from './utils'; export const sendNewMessage = async (window: Page, sessionid: string, message: string) => { @@ -9,5 +9,5 @@ export const sendNewMessage = async (window: Page, sessionid: string, message: s await typeIntoInput(window, 'new-session-conversation', sessionid); // click next await clickOnTestIdWithText(window, 'next-new-conversation-button', 'Next'); - await messageSent(window, message); + await sendMessage(window, message); }; diff --git a/ts/test/automation/utilities/utils.ts b/ts/test/automation/utilities/utils.ts index 34886072a..9f6c1fab4 100644 --- a/ts/test/automation/utilities/utils.ts +++ b/ts/test/automation/utilities/utils.ts @@ -1,19 +1,57 @@ +import { ElementHandle } from '@playwright/test'; import { Page } from 'playwright-core'; +import { sleepFor } from '../../../session/utils/Promise'; +import { Strategy } from '../types/testing'; // tslint:disable: no-console +// WAIT FOR FUNCTIONS + export async function waitForTestIdWithText(window: Page, dataTestId: string, text?: string) { let builtSelector = `css=[data-testid=${dataTestId}]`; if (text) { - builtSelector += `:has-text("${text}")`; - } + // " => \\\" + /* prettier-ignore */ + // tslint:disable-next-line: quotemark + const escapedText = text.replace(/"/g, '\\\"'); - console.info('looking for selector', builtSelector); + builtSelector += `:has-text("${escapedText}")`; + console.warn('builtSelector:', builtSelector); + // console.warn('Text is tiny bubble: ', escapedText); + } + // console.info('looking for selector', builtSelector); const found = await window.waitForSelector(builtSelector, { timeout: 55000 }); - console.info('found selector', builtSelector); + // console.info('found selector', builtSelector); return found; } +export async function waitForElement( + window: Page, + strategy: Strategy, + selector: string, + maxWaitMs?: number +) { + const builtSelector = `css=[${strategy}=${selector}]`; + + return window.waitForSelector(builtSelector, { timeout: maxWaitMs }); +} + +export async function waitForTextMessage(window: Page, text: string, maxWait?: number) { + let builtSelector = `:has-text("${text}")`; + if (text) { + // " => \\\" + /* prettier-ignore */ + // tslint:disable-next-line: quotemark + const escapedText = text.replace(/"/g, '\\\"'); + + builtSelector += `:has-text("${escapedText}")`; + console.warn('builtSelector:', builtSelector); + // console.warn('Text is tiny bubble: ', escapedText); + } + const el = await window.waitForSelector(builtSelector, { timeout: maxWait }); + return el; +} + export async function waitForControlMessageWithText(window: Page, text: string) { return waitForTestIdWithText(window, 'control-message', text); } @@ -27,12 +65,48 @@ export async function waitForMatchingText(window: Page, text: string) { console.info(`got matchingText: ${text}`); } +export async function waitForLoadingAnimationToFinish(window: Page) { + let loadingAnimation: ElementHandle | undefined; + + await waitForElement(window, 'data-testid', 'loading-animation'); + + do { + try { + loadingAnimation = await waitForElement(window, 'data-testid', 'loading-animation', 100); + await sleepFor(100); + console.info('loading-animation was found, waiting for it to be gone'); + } catch (e) { + loadingAnimation = undefined; + } + } while (loadingAnimation); +} +console.info('Loading animation has finished'); + +// ACTIONS + +export async function clickOnElement( + window: Page, + strategy: Strategy, + selector: string, + maxWait?: number +) { + const builtSelector = `css=[${strategy}=${selector}]`; + await window.waitForSelector(builtSelector, { timeout: maxWait }); + await window.click(builtSelector); + return; +} + export async function clickOnMatchingText(window: Page, text: string, rightButton = false) { console.info(`clickOnMatchingText: "${text}"`); return window.click(`"${text}"`, rightButton ? { button: 'right' } : undefined); } -export async function clickOnTestIdWithText(window: Page, dataTestId: string, text?: string) { +export async function clickOnTestIdWithText( + window: Page, + dataTestId: string, + text?: string, + rightButton?: boolean +) { console.info(`clickOnTestIdWithText with testId:${dataTestId} and text:${text ? text : 'none'}`); const builtSelector = !text @@ -40,7 +114,7 @@ export async function clickOnTestIdWithText(window: Page, dataTestId: string, te : `css=[data-testid=${dataTestId}]:has-text("${text}")`; await window.waitForSelector(builtSelector); - return window.click(builtSelector); + return window.click(builtSelector, rightButton ? { button: 'right' } : undefined); } export function getMessageTextContentNow() { @@ -50,5 +124,5 @@ export function getMessageTextContentNow() { export async function typeIntoInput(window: Page, dataTestId: string, text: string) { console.info(`typeIntoInput testId: ${dataTestId} : "${text}"`); const builtSelector = `css=[data-testid=${dataTestId}]`; - return window.fill(builtSelector, text); + return window.type(builtSelector, text); }