diff --git a/package.json b/package.json index 10db363..3ed4301 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "fs-extra": "^11.1.1", "lodash": "^4.17.21", "prettier": "^3.0.1", - "typescript": "^5.1.6", + "semver": "^7.5.4", + "typescript": "^5.5.4", "uuid": "^9.0.1" }, "scripts": { @@ -38,5 +39,8 @@ "allure-create": "allure generate ./allure-results", "allure-open": "allure open ./allure-report" }, - "dependencies": {} + "dependencies": { + "@session-foundation/qa-seeder": "^0.0.11", + "buffer-crc32": "^1.0.0" + } } diff --git a/sessionReporter.ts b/sessionReporter.ts index e153a06..31eefc4 100644 --- a/sessionReporter.ts +++ b/sessionReporter.ts @@ -8,7 +8,7 @@ import type { TestResult, } from '@playwright/test/reporter'; import chalk from 'chalk'; -import { Dictionary, groupBy, isString, mean, sortBy } from 'lodash'; +import { Dictionary, groupBy, isString, mean, omit, sortBy } from 'lodash'; type TestAndResult = { test: TestCase; result: TestResult }; @@ -248,7 +248,10 @@ class SessionReporter implements Reporter { } onError?(error: TestError) { - console.info('global error:', error); + console.info('global error:', omit(error, ['stack', 'snippet'])); + error.stack?.split('\n').forEach((line) => { + process.stderr.write(`${line}\n`); + }); } } diff --git a/tests/automation/constants/variables.ts b/tests/automation/constants/variables.ts index 471638f..02bd011 100644 --- a/tests/automation/constants/variables.ts +++ b/tests/automation/constants/variables.ts @@ -41,30 +41,38 @@ export const mediaArray = [ ]; type DisappearingOption = { + durationSeconds: number; timeOption: DMTimeOption; disappearingMessagesType: DisappearType | DisappearGroupType; disappearAction: DisappearActions; }; +const duration10Seconds = 10; +const duration30Seconds = 30; + export const defaultDisappearingOptions = { DAS: { - timeOption: 'time-option-30-seconds', + durationSeconds: duration30Seconds, + timeOption: `time-option-${duration30Seconds}-seconds`, disappearingMessagesType: 'disappear-after-send-option', disappearAction: 'sent', }, DAR: { - timeOption: 'time-option-10-seconds', + durationSeconds: duration10Seconds, + timeOption: `time-option-${duration10Seconds}-seconds`, disappearingMessagesType: 'disappear-after-read-option', disappearAction: 'read', }, group: { - timeOption: 'time-option-10-seconds', + durationSeconds: duration10Seconds, + timeOption: `time-option-${duration10Seconds}-seconds`, disappearingMessagesType: 'disappear-after-send-option' satisfies DisappearGroupType, disappearAction: 'sent', }, NTS: { - timeOption: 'time-option-10-seconds', + durationSeconds: duration10Seconds, + timeOption: `time-option-${duration10Seconds}-seconds`, disappearingMessagesType: 'disappear-after-send-option', disappearAction: 'sent', }, diff --git a/tests/automation/group_disappearing_messages.spec.ts b/tests/automation/group_disappearing_messages.spec.ts index 155d0bf..22c4d89 100644 --- a/tests/automation/group_disappearing_messages.spec.ts +++ b/tests/automation/group_disappearing_messages.spec.ts @@ -1,3 +1,4 @@ +import { buildStateForTest } from '@session-foundation/qa-seeder'; import { sleepFor } from '../promise_utils'; import { defaultDisappearingOptions, @@ -5,7 +6,11 @@ import { mediaArray, testLink, } from './constants/variables'; -import { test_group_Alice_1W_Bob_1W_Charlie_1W } from './setup/sessionTest'; +import { recoverFromSeed } from './setup/recovery_using_seed'; +import { + sessionTestThreeWindows, + test_group_Alice_1W_Bob_1W_Charlie_1W, +} from './setup/sessionTest'; import { sendMessage } from './utilities/message'; import { sendLinkPreview, @@ -23,12 +28,16 @@ import { } from './utilities/utils'; // Disappearing time settings for all tests -const { timeOption, disappearingMessagesType, disappearAction } = - defaultDisappearingOptions.group; +const { + durationSeconds, + timeOption, + disappearingMessagesType, + disappearAction, +} = defaultDisappearingOptions.group; mediaArray.forEach(({ mediaType, path }) => { test_group_Alice_1W_Bob_1W_Charlie_1W( - `Send disappearing ${mediaType} groups`, + `PLIP Send disappearing ${mediaType} groups`, async ({ alice, aliceWindow1, @@ -58,7 +67,7 @@ mediaArray.forEach(({ mediaType, path }) => { waitForTestIdWithText(bobWindow1, 'audio-player'), waitForTestIdWithText(charlieWindow1, 'audio-player'), ]); - await sleepFor(30000); + await sleepFor(durationSeconds * 1000, true); await Promise.all([ hasElementBeenDeleted(bobWindow1, 'data-testid', 'audio-player'), hasElementBeenDeleted(charlieWindow1, 'data-testid', 'audio-player'), @@ -68,11 +77,11 @@ mediaArray.forEach(({ mediaType, path }) => { waitForTextMessage(bobWindow1, testMessage), waitForTextMessage(charlieWindow1, testMessage), ]); - // Wait 30 seconds for image to disappear - await sleepFor(30000); + // Wait durationSeconds seconds for image to disappear + await sleepFor(durationSeconds * 1000, true); await Promise.all([ - hasTextMessageBeenDeleted(bobWindow1, testMessage), - hasTextMessageBeenDeleted(charlieWindow1, testMessage), + hasTextMessageBeenDeleted(bobWindow1, testMessage, 1000), + hasTextMessageBeenDeleted(charlieWindow1, testMessage, 1000), ]); } }, @@ -146,3 +155,62 @@ test_group_Alice_1W_Bob_1W_Charlie_1W( ]); }, ); + +mediaArray.forEach(({ mediaType, path }) => { + sessionTestThreeWindows( + `PLOP Send disappearing ${mediaType} groups`, + async (windows, testInfo) => { + const { group, users } = await buildStateForTest( + '3friendsInGroup', + testInfo.title, + ); + await Promise.all( + windows.map((w, index) => recoverFromSeed(w, users[index].seedPhrase)), + ); + const [aliceWindow1, bobWindow1, charlieWindow1] = windows; + + const [alice] = users; + await sleepFor(15000); + + const testMessage = `${alice.userName} sending ${mediaType} to ${group.groupName}`; + await setDisappearingMessages(aliceWindow1, [ + 'group', + disappearingMessagesType, + timeOption, + disappearAction, + ]); + // Send media + if (mediaType === 'voice') { + await sendVoiceMessage(aliceWindow1); + } else { + await sendMedia(aliceWindow1, path, testMessage); + } + await Promise.all([ + waitForLoadingAnimationToFinish(bobWindow1, 'loading-animation'), + waitForLoadingAnimationToFinish(charlieWindow1, 'loading-animation'), + ]); + if (mediaType === 'voice') { + await Promise.all([ + waitForTestIdWithText(bobWindow1, 'audio-player'), + waitForTestIdWithText(charlieWindow1, 'audio-player'), + ]); + await sleepFor(durationSeconds * 1000, true); + await Promise.all([ + hasElementBeenDeleted(bobWindow1, 'data-testid', 'audio-player'), + hasElementBeenDeleted(charlieWindow1, 'data-testid', 'audio-player'), + ]); + } else { + await Promise.all([ + waitForTextMessage(bobWindow1, testMessage), + waitForTextMessage(charlieWindow1, testMessage), + ]); + // Wait durationSeconds seconds for image to disappear + await sleepFor(durationSeconds * 1000, true); + await Promise.all([ + hasTextMessageBeenDeleted(bobWindow1, testMessage), + hasTextMessageBeenDeleted(charlieWindow1, testMessage), + ]); + } + }, + ); +}); diff --git a/tests/automation/linked_device_group.spec.ts b/tests/automation/linked_device_group.spec.ts index f314ffe..fa91dd1 100644 --- a/tests/automation/linked_device_group.spec.ts +++ b/tests/automation/linked_device_group.spec.ts @@ -8,7 +8,6 @@ import { } from './setup/sessionTest'; import { leaveGroup } from './utilities/leave_group'; import { - checkModalStrings, clickOnMatchingText, clickOnTestIdWithText, waitForLoadingAnimationToFinish, diff --git a/tests/automation/setup/sessionTest.ts b/tests/automation/setup/sessionTest.ts index ab701e2..7c341cf 100644 --- a/tests/automation/setup/sessionTest.ts +++ b/tests/automation/setup/sessionTest.ts @@ -7,23 +7,16 @@ import { Group, User } from '../types/testing'; import { linkedDevice } from '../utilities/linked_device'; import { forceCloseAllWindows } from './closeWindows'; import { createGroup } from './create_group'; -import { newUser } from './new_user'; import { openApp } from './open'; import chalk from 'chalk'; +import type { Tuple } from '../../../types/tuple'; +import { newUser } from './new_user'; +import { usernames } from '@session-foundation/qa-seeder'; // This is not ideal, most of our test needs to open a specific number of windows and close them once the test is done or failed. // This file contains a bunch of utility function to use to open those windows and clean them afterwards. // Note: those function only keep track (and close) the windows they open. If you open a new window or need to close and reopen an existing one, this won't take of it. -type Tuple = N extends N - ? number extends N - ? T[] - : _TupleOf - : never; -type _TupleOf = R['length'] extends N - ? R - : _TupleOf; - type CountWindows = 1 | 2 | 3 | 4 | 5; type WithAlice = { alice: User }; @@ -44,7 +37,7 @@ function sessionTest>( testCallback: (windows: N, testInfo: TestInfo) => Promise, count: T, ) { - return test(testName, async ({}, testinfo) => { + return test(testName, async ({}, testInfo) => { const windows = await openApp(count); try { @@ -53,7 +46,7 @@ function sessionTest>( `openApp should have opened ${count} windows but did not.`, ); } - await testCallback(windows as N, testinfo); + await testCallback(windows as N, testInfo); } catch (e) { throw e; } finally { @@ -82,11 +75,10 @@ export function sessionTestTwoWindows( export function sessionTestThreeWindows( testName: string, - testCallback: ([windowA, windowB, windowC]: [ - Page, - Page, - Page, - ]) => Promise, + testCallback: ( + [windowA, windowB, windowC]: [Page, Page, Page], + testInfo: TestInfo, + ) => Promise, ) { return sessionTest(testName, testCallback, 3); } @@ -130,22 +122,17 @@ function sessionTestGeneric< testInfo: TestInfo, ) => Promise, ) { - const userNames: Tuple = ['Alice', 'Bob', 'Charlie', 'Dracula']; - - return test(testName, async ({}, testinfo) => { - const mainWindows = await openApp(userCount); + return test(testName, async ({}, testInfo) => { + let mainWindows: Array = []; const linkedWindows: Array = []; try { - if (mainWindows.length !== userCount) { - throw new Error( - `openApp should have opened ${userCount} windows but did not.`, - ); - } + let groupCreated: Group | undefined; + + mainWindows = await openApp(userCount); const users = (await Promise.all( - mainWindows.map((m, i) => newUser(m, userNames[i], waitForNetwork)), + mainWindows.map((m, i) => newUser(m, usernames[i], waitForNetwork)), )) as Tuple; - if (links?.length) { for (let index = 0; index < links.length; index++) { const link = links[index]; @@ -158,8 +145,6 @@ function sessionTestGeneric< linkedWindows.push(linked); } } - - let groupCreated: Group | undefined; if (grouped?.length) { groupCreated = await createGroup( testName, @@ -182,7 +167,7 @@ function sessionTestGeneric< ? Group : undefined, }, - testinfo, + testInfo, ); } catch (e) { throw e; @@ -201,14 +186,14 @@ function sessionTestGeneric< * Used for tests which don't need network (i.e. setting/checking passwords etc) */ export function test_Alice_1W_no_network( - testname: string, + testName: string, testCallback: ( details: WithAlice & WithAliceWindow1, testInfo: TestInfo, ) => Promise, ) { return sessionTestGeneric( - testname, + testName, 1, { waitForNetwork: false }, ({ mainWindows, users }, testInfo) => { @@ -228,14 +213,14 @@ export function test_Alice_1W_no_network( * - Alice with 2 windows. */ export function test_Alice_2W( - testname: string, + testName: string, testCallback: ( details: WithAlice & WithAliceWindow1 & WithAliceWindow2, testInfo: TestInfo, ) => Promise, ) { return sessionTestGeneric( - testname, + testName, 1, { links: [1] }, ({ mainWindows, users, linkedWindows }, testInfo) => { @@ -257,14 +242,14 @@ export function test_Alice_2W( * - Bob with 1 window. */ export function test_Alice_1W_Bob_1W( - testname: string, + testName: string, testCallback: ( details: WithAlice & WithAliceWindow1 & WithBob & WithBobWindow1, testInfo: TestInfo, ) => Promise, ) { return sessionTestGeneric( - testname, + testName, 2, {}, ({ mainWindows, users }, testInfo) => { @@ -287,7 +272,7 @@ export function test_Alice_1W_Bob_1W( * - Bob with 1 window. */ export function test_Alice_2W_Bob_1W( - testname: string, + testName: string, testCallback: ( details: WithAlice & WithAliceWindow1 & @@ -298,7 +283,7 @@ export function test_Alice_2W_Bob_1W( ) => Promise, ) { return sessionTestGeneric( - testname, + testName, 2, { links: [1] }, ({ mainWindows, users, linkedWindows }, testInfo) => { @@ -323,7 +308,7 @@ export function test_Alice_2W_Bob_1W( * - Charlie with 1 window. */ export function test_group_Alice_1W_Bob_1W_Charlie_1W( - testname: string, + testName: string, testCallback: ( details: WithAlice & WithAliceWindow1 & @@ -336,7 +321,7 @@ export function test_group_Alice_1W_Bob_1W_Charlie_1W( ) => Promise, ) { return sessionTestGeneric( - testname, + testName, 3, { grouped: [1, 2, 3] }, ({ mainWindows, users, groupCreated }, testInfo) => { @@ -363,7 +348,7 @@ export function test_group_Alice_1W_Bob_1W_Charlie_1W( * - Charlie with 1 window. */ export function test_group_Alice_2W_Bob_1W_Charlie_1W( - testname: string, + testName: string, testCallback: ( details: WithAlice & WithAliceWindow1 & @@ -377,7 +362,7 @@ export function test_group_Alice_2W_Bob_1W_Charlie_1W( ) => Promise, ) { return sessionTestGeneric( - testname, + testName, 3, { grouped: [1, 2, 3], links: [1] }, ({ mainWindows, users, groupCreated, linkedWindows }, testInfo) => { @@ -406,7 +391,7 @@ export function test_group_Alice_2W_Bob_1W_Charlie_1W( * - Dracula with 1 window, */ export function test_group_Alice_1W_Bob_1W_Charlie_1W_Dracula_1W( - testname: string, + testName: string, testCallback: ( details: WithAlice & WithAliceWindow1 & @@ -422,7 +407,7 @@ export function test_group_Alice_1W_Bob_1W_Charlie_1W_Dracula_1W( ) => Promise, ) { return sessionTestGeneric( - testname, + testName, 4, { grouped: [1, 2, 3] }, ({ mainWindows, users, groupCreated }, testInfo) => { diff --git a/tests/automation/test.spec.ts b/tests/automation/test.spec.ts index 167c617..ee3ea99 100644 --- a/tests/automation/test.spec.ts +++ b/tests/automation/test.spec.ts @@ -1,6 +1,19 @@ -import { sessionTestOneWindow } from './setup/sessionTest'; -import { clickOnTestIdWithText } from './utilities/utils'; +import { buildStateForTest } from '@session-foundation/qa-seeder'; +import { sleepFor } from '../promise_utils'; +import { recoverFromSeed } from './setup/recovery_using_seed'; +import { sessionTestThreeWindows } from './setup/sessionTest'; +import { clickOnMatchingText } from './utilities/utils'; -sessionTestOneWindow('Tiny test', async ([windowA]) => { - await clickOnTestIdWithText(windowA, 'create-account-button'); +sessionTestThreeWindows('Tiny test', async (windows, testInfo) => { + const [windowA] = windows; + const prebuilt = await buildStateForTest('3friendsInGroup', testInfo.title); + + await Promise.all( + windows.map((w, index) => + recoverFromSeed(w, prebuilt.users[index].seedPhrase), + ), + ); + + await sleepFor(1000000); + await clickOnMatchingText(windowA, 'Create Session ID'); }); diff --git a/tsconfig.json b/tsconfig.json index 866677f..c625b4b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,21 +2,26 @@ "compilerOptions": { "sourceMap": true, "noEmit": true, - "target": "esnext", "lib": ["dom", "es2021"], "rootDir": "./", "removeComments": true, "importHelpers": false, - "strict": true, - "skipLibCheck": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "moduleResolution": "node", + "allowJs": true, + "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "useUnknownInCatchVariables": false, + "inlineSourceMap": false, + "module": "CommonJS", + "target": "es6", + "outDir": "./dist", + "strict": true, "esModuleInterop": true, - "inlineSourceMap": false + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true } } diff --git a/types/tuple.d.ts b/types/tuple.d.ts new file mode 100644 index 0000000..a83aae5 --- /dev/null +++ b/types/tuple.d.ts @@ -0,0 +1,11 @@ +type TupleOf< + T, + N extends number, + R extends Array, +> = R['length'] extends N ? R : TupleOf; + +export type Tuple = N extends N + ? number extends N + ? Array + : TupleOf + : never; diff --git a/yarn.lock b/yarn.lock index 41ee1c7..0f0386e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -101,6 +101,45 @@ dependencies: playwright "1.51.1" +"@session-foundation/basic-types@0.0.2", "@session-foundation/basic-types@^0.0.2": + version "0.0.2" + resolved "http://vps-cfbca183.vps.ovh.ca:4873/@session-foundation/basic-types/-/basic-types-0.0.2.tgz#182b086ea5d0e40b36ce9a2affde20d9d11c2e02" + integrity sha512-ZavLQWkoLuDRh3HQsHX1OwHoNsRu8D+jpp1z06E8XPuJgWhvCISLfs1RQ5ESZT+xE6lbSUfe4pfXYg3cRvE1+A== + dependencies: + buffer-crc32 "^1.0.0" + +"@session-foundation/mnemonic@^0.0.5": + version "0.0.5" + resolved "http://vps-cfbca183.vps.ovh.ca:4873/@session-foundation/mnemonic/-/mnemonic-0.0.5.tgz#c7c220d94e33f495b1469194a233f5d210874a82" + integrity sha512-2WcV7a9Ph8Xh0bxaHULlVZpxmCsuAkYgui3JZQDoUH2jnA7TrxiPtWCCItvEte2akhy4I2Z6XvSUZfXZBtuYgw== + dependencies: + buffer-crc32 "^1.0.0" + +"@session-foundation/qa-seeder@^0.0.11": + version "0.0.11" + resolved "http://vps-cfbca183.vps.ovh.ca:4873/@session-foundation/qa-seeder/-/qa-seeder-0.0.11.tgz#9307cd198569d97bee820e6ca183b574d0217705" + integrity sha512-YSW7j525Ue8IsZpjSUGkzD71QAkLUNVJn/V/RTMbl+91tZoQVXOP12PnTxDQYLWiC5QqpLMHx69cDegPuF1bQg== + dependencies: + "@session-foundation/basic-types" "^0.0.2" + "@session-foundation/mnemonic" "^0.0.5" + "@session-foundation/session-tooling" "^0.0.10" + "@session-foundation/sodium" "^0.0.2" + lodash "^4.17.21" + +"@session-foundation/session-tooling@^0.0.10": + version "0.0.10" + resolved "http://vps-cfbca183.vps.ovh.ca:4873/@session-foundation/session-tooling/-/session-tooling-0.0.10.tgz#1c66d91b2f436f14c8f411d32827130cbbe20bda" + integrity sha512-0kMftog2hUbsv7E34w0YOmxc3XBj04vGQ6S5UwKyQU2jemQnhVmquBHTmDWChhYAtD7Oc13Lvvxh47o2+wvynQ== + +"@session-foundation/sodium@^0.0.2": + version "0.0.2" + resolved "http://vps-cfbca183.vps.ovh.ca:4873/@session-foundation/sodium/-/sodium-0.0.2.tgz#24d0c232d99bce328e37928a2246bae89d969c13" + integrity sha512-+FfRIiqOstTzkp+vANGIRtzaCp/SCgs9w3Z6sDp6IyDkEBxTBCcGoTXPAc0o/mGv7+s91RPNJgHtiSvpfpf8pQ== + dependencies: + "@session-foundation/basic-types" "0.0.2" + buffer-crc32 "^1.0.0" + libsodium-wrappers-sumo "^0.7.15" + "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" @@ -441,6 +480,11 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" +buffer-crc32@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" + integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1423,6 +1467,18 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +libsodium-sumo@^0.7.15: + version "0.7.15" + resolved "https://registry.yarnpkg.com/libsodium-sumo/-/libsodium-sumo-0.7.15.tgz#91c1d863fe3fbce6d6b9db1aadaa622733a1d007" + integrity sha512-5tPmqPmq8T8Nikpm1Nqj0hBHvsLFCXvdhBFV7SGOitQPZAA6jso8XoL0r4L7vmfKXr486fiQInvErHtEvizFMw== + +libsodium-wrappers-sumo@^0.7.15: + version "0.7.15" + resolved "https://registry.yarnpkg.com/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.15.tgz#0ef2a99b4b17e8385aa7e6850593660dbaf5fb40" + integrity sha512-aSWY8wKDZh5TC7rMvEdTHoyppVq/1dTSAeAR7H6pzd6QRT3vQWcT5pGwCotLcpPEOLXX6VvqihSPkpEhYAjANA== + dependencies: + libsodium-sumo "^0.7.15" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -2004,10 +2060,10 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typescript@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +typescript@^5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== unbox-primitive@^1.0.2: version "1.0.2"