From 3bf0ec21da05915e9ccb1ad60ee3cd5e416d64b7 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Wed, 8 Oct 2025 17:40:50 +1100 Subject: [PATCH 1/3] feat: add message limit tests --- .../automation/enforce_localized_str.spec.ts | 5 +- tests/automation/message_checks.spec.ts | 100 ++++++++++++++++++ tests/automation/types/testing.ts | 1 + tests/automation/utilities/utils.ts | 7 +- 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/tests/automation/enforce_localized_str.spec.ts b/tests/automation/enforce_localized_str.spec.ts index ec9dc63..deb04dc 100644 --- a/tests/automation/enforce_localized_str.spec.ts +++ b/tests/automation/enforce_localized_str.spec.ts @@ -259,7 +259,10 @@ function getExpectedStringFromKey( return "You don't have any conversations yet"; case 'onboardingHitThePlusButton': return 'Hit the plus button to start a chat, create a group, or join an official community!'; - + case 'modalMessageTooLongTitle': + return 'Message Too Long'; + case 'modalMessageTooLongDescription': + return 'Please shorten your message to {limit} characters or less.'; default: // returning null means we don't have an expected string yet for this key. // This will make the test fail diff --git a/tests/automation/message_checks.spec.ts b/tests/automation/message_checks.spec.ts index bb31894..bc7b19a 100644 --- a/tests/automation/message_checks.spec.ts +++ b/tests/automation/message_checks.spec.ts @@ -10,6 +10,7 @@ import { } from './locators'; import { newUser } from './setup/new_user'; import { + sessionTestOneWindow, sessionTestTwoWindows, test_Alice_1W_Bob_1W, } from './setup/sessionTest'; @@ -24,6 +25,7 @@ import { trustUser, } from './utilities/send_media'; import { + checkModalStrings, clickOn, clickOnElement, clickOnMatchingText, @@ -255,3 +257,101 @@ sessionTestTwoWindows( console.log(timesArray); }, ); + +// Message length limit tests (pre-pro) +const maxChars = 2000; +const countdownThreshold = 1800; + +const messageLengthTestCases = [ + { + length: 1799, + char: 'a', + shouldSend: true, + }, + { + length: 1800, + char: 'b', + shouldSend: true, + }, + { + length: 2000, + char: 'c', + shouldSend: true, + }, + { + length: 2001, + char: 'd', + shouldSend: false, + }, +]; + +messageLengthTestCases.forEach((testCase) => { + sessionTestOneWindow( + `Message length limit (${testCase.length} chars)`, + async ([aliceWindow1]) => { + const alice = await newUser(aliceWindow1, 'Alice', false); + const expectedCount = + testCase.length < countdownThreshold + ? null + : (maxChars - testCase.length).toString(); + const message = testCase.char.repeat(testCase.length); + await clickOn(aliceWindow1, HomeScreen.plusButton); + await clickOn(aliceWindow1, HomeScreen.newMessageOption); + await typeIntoInput(aliceWindow1, 'new-session-conversation', alice.accountid); + await clickOn(aliceWindow1, HomeScreen.newMessageNextButton); + // Type the message + await typeIntoInput(aliceWindow1, 'message-input-text-area', message, true); + + // Check countdown behavior + if (expectedCount) { + await waitForTestIdWithText( + aliceWindow1, + 'tooltip-character-count', + expectedCount, + ); + } else { + // Verify countdown tooltip is not present + try { + await waitForElement( + aliceWindow1, + 'data-testid', + 'tooltip-character-count', + 1000, + ); + throw new Error( + `Countdown should not be visible for messages under ${countdownThreshold} chars`, + ); + } catch (e) { + // Expected - countdown should not exist + console.log('Countdown not present as expected'); + } + } + + // Try to send + await clickOn(aliceWindow1, Conversation.sendMessageButton); + + if (testCase.shouldSend) { + // Message should appear in Alice's window + await waitForTextMessage(aliceWindow1, message); + } else { + // Message Too Long modal + await checkModalStrings( + aliceWindow1, + englishStrippedStr('modalMessageTooLongTitle').toString(), + englishStrippedStr('modalMessageTooLongDescription') + .withArgs({ limit: maxChars.toLocaleString('en-AU') }) // With .toString() the test expects "2000" not "2,000" + .toString(), + ); + await clickOn(aliceWindow1, Global.confirmButton); + + // Verify message didn't send + try { + await waitForTextMessage(aliceWindow1, message, 2000); + throw new Error('Message should not have been sent'); + } catch (e) { + console.log(`Message didn't send as expected`); + } + } + }, + ); +}); diff --git a/tests/automation/types/testing.ts b/tests/automation/types/testing.ts index c7c2d33..440abd7 100644 --- a/tests/automation/types/testing.ts +++ b/tests/automation/types/testing.ts @@ -186,6 +186,7 @@ export type DataTestId = | 'set-password-settings-button' | 'settings-section' | 'theme-section' + | 'tooltip-character-count' | 'unblock-button-settings-screen' | 'update-group-info-name-input' | 'update-profile-info-name-input' diff --git a/tests/automation/utilities/utils.ts b/tests/automation/utilities/utils.ts index ed7428f..62df7da 100644 --- a/tests/automation/utilities/utils.ts +++ b/tests/automation/utilities/utils.ts @@ -373,6 +373,7 @@ export async function typeIntoInput( window: Page, dataTestId: DataTestId, text: string, + paste?: boolean, // typing long messages hits the runner timeout ) { console.info(`typeIntoInput testId: ${dataTestId} : "${text}"`); const builtSelector = `css=[data-testid=${dataTestId}]`; @@ -382,7 +383,11 @@ export async function typeIntoInput( await clickOn(window, locator); // reset the content to be empty before typing into the input await window.fill(builtSelector, ''); - return window.type(builtSelector, text); + if (paste) { + await window.fill(builtSelector, text); + } else { + await window.type(builtSelector, text); + } } export async function doesTextIncludeString( From f5aa86e2f5733764247a33af9fff0772524ddade Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 20 Oct 2025 10:14:28 +1100 Subject: [PATCH 2/3] feat: add blinded message request tests --- .../automation/enforce_localized_str.spec.ts | 4 +- tests/automation/locators/index.ts | 3 + tests/automation/message_checks.spec.ts | 15 +++- tests/automation/message_requests.spec.ts | 82 +++++++++++++++++++ tests/automation/types/testing.ts | 1 + tests/automation/utilities/utils.ts | 6 +- 6 files changed, 103 insertions(+), 8 deletions(-) diff --git a/tests/automation/enforce_localized_str.spec.ts b/tests/automation/enforce_localized_str.spec.ts index deb04dc..98f7a3b 100644 --- a/tests/automation/enforce_localized_str.spec.ts +++ b/tests/automation/enforce_localized_str.spec.ts @@ -259,9 +259,9 @@ function getExpectedStringFromKey( return "You don't have any conversations yet"; case 'onboardingHitThePlusButton': return 'Hit the plus button to start a chat, create a group, or join an official community!'; - case 'modalMessageTooLongTitle': + case 'modalMessageTooLongTitle': return 'Message Too Long'; - case 'modalMessageTooLongDescription': + case 'modalMessageTooLongDescription': return 'Please shorten your message to {limit} characters or less.'; default: // returning null means we don't have an expected string yet for this key. diff --git a/tests/automation/locators/index.ts b/tests/automation/locators/index.ts index 7db611a..5279174 100644 --- a/tests/automation/locators/index.ts +++ b/tests/automation/locators/index.ts @@ -159,6 +159,9 @@ export class Settings extends Locator { ); static readonly confirmPasswordInput = this.testId('password-input-confirm'); static readonly enableCalls = this.testId('enable-calls-settings-row'); + static readonly enableCommunityMessageRequests = this.testId( + 'enable-communities-message-requests-settings-row', + ); static readonly enableMicrophone = this.testId( 'enable-microphone-settings-row', ); diff --git a/tests/automation/message_checks.spec.ts b/tests/automation/message_checks.spec.ts index bc7b19a..ab12ef9 100644 --- a/tests/automation/message_checks.spec.ts +++ b/tests/automation/message_checks.spec.ts @@ -297,10 +297,19 @@ messageLengthTestCases.forEach((testCase) => { const message = testCase.char.repeat(testCase.length); await clickOn(aliceWindow1, HomeScreen.plusButton); await clickOn(aliceWindow1, HomeScreen.newMessageOption); - await typeIntoInput(aliceWindow1, 'new-session-conversation', alice.accountid); + await typeIntoInput( + aliceWindow1, + 'new-session-conversation', + alice.accountid, + ); await clickOn(aliceWindow1, HomeScreen.newMessageNextButton); // Type the message - await typeIntoInput(aliceWindow1, 'message-input-text-area', message, true); + await typeIntoInput( + aliceWindow1, + 'message-input-text-area', + message, + true, // Paste because otherwise Playwright times out + ); // Check countdown behavior if (expectedCount) { @@ -339,7 +348,7 @@ messageLengthTestCases.forEach((testCase) => { aliceWindow1, englishStrippedStr('modalMessageTooLongTitle').toString(), englishStrippedStr('modalMessageTooLongDescription') - .withArgs({ limit: maxChars.toLocaleString('en-AU') }) // With .toString() the test expects "2000" not "2,000" + .withArgs({ limit: maxChars.toLocaleString('en-AU') }) // Force "2,000" instead of "2000" .toString(), ); await clickOn(aliceWindow1, Global.confirmButton); diff --git a/tests/automation/message_requests.spec.ts b/tests/automation/message_requests.spec.ts index 21d22f2..9ac4297 100644 --- a/tests/automation/message_requests.spec.ts +++ b/tests/automation/message_requests.spec.ts @@ -1,3 +1,5 @@ +import { expect } from '@playwright/test'; + import { englishStrippedStr } from '../localization/englishStrippedStr'; import { Conversation, @@ -7,6 +9,7 @@ import { Settings, } from './locators'; import { test_Alice_1W_Bob_1W } from './setup/sessionTest'; +import { joinCommunity } from './utilities/join_community'; import { sendMessage } from './utilities/message'; import { sendNewMessage } from './utilities/send_message'; import { @@ -14,6 +17,7 @@ import { clickOn, clickOnMatchingText, clickOnWithText, + grabTextFromElement, waitForMatchingText, waitForTestIdWithText, } from './utilities/utils'; @@ -164,3 +168,81 @@ test_Alice_1W_Bob_1W( ); }, ); + +test_Alice_1W_Bob_1W( + 'Community message requests on', + async ({ alice, aliceWindow1, bob, bobWindow1 }) => { + await clickOn(bobWindow1, LeftPane.settingsButton); + await clickOn(bobWindow1, Settings.privacyMenuItem); + await clickOn(bobWindow1, Settings.enableCommunityMessageRequests); + await clickOn(bobWindow1, Global.modalCloseButton); + await Promise.all([joinCommunity(aliceWindow1), joinCommunity(bobWindow1)]); + const communityMsg = `I accept message requests + ${Date.now()}`; + await sendMessage(bobWindow1, communityMsg); + await clickOn(aliceWindow1, Conversation.scrollToBottomButton); + // Using native methods to locate the author corresponding to the sent message + await aliceWindow1 + .locator('.module-message__container', { hasText: communityMsg }) + .locator('..') // Go up to parent + .locator('svg') + .click(); + const elText = await grabTextFromElement( + aliceWindow1, + 'data-testid', + 'account-id', + ); + expect(elText).toMatch(/^15/); + await clickOn(aliceWindow1, HomeScreen.newMessageAccountIDInput); // yes this is the actual locator for the 'Message' button + await waitForTestIdWithText( + aliceWindow1, + 'header-conversation-name', + bob.userName, + ); + const messageRequestMsg = `${alice.userName} to ${bob.userName}`; + const testReply = `${bob.userName} accepts message request`; + await sendMessage(aliceWindow1, messageRequestMsg); + await clickOn(bobWindow1, HomeScreen.messageRequestBanner); + // Select message request from User A + await clickOnWithText( + bobWindow1, + HomeScreen.conversationItemName, + alice.userName, + ); + await sendMessage(bobWindow1, testReply); + // Check config message of message request acceptance + await waitForTestIdWithText( + bobWindow1, + 'message-request-response-message', + englishStrippedStr('messageRequestYouHaveAccepted') + .withArgs({ + name: alice.userName, + }) + .toString(), + ); + }, +); +test_Alice_1W_Bob_1W( + 'Community message requests off', + async ({ aliceWindow1, bobWindow1 }) => { + await Promise.all([joinCommunity(aliceWindow1), joinCommunity(bobWindow1)]); + const communityMsg = `I do not accept message requests + ${Date.now()}`; + await sendMessage(bobWindow1, communityMsg); + await clickOn(aliceWindow1, Conversation.scrollToBottomButton); + // Using native methods to locate the author corresponding to the sent message + await aliceWindow1 + .locator('.module-message__container', { hasText: communityMsg }) + .locator('..') // Go up to parent + .locator('svg') + .click(); + const elText = await grabTextFromElement( + aliceWindow1, + 'data-testid', + 'account-id', + ); + expect(elText).toMatch(/^15/); + const messageButton = aliceWindow1.getByTestId( + HomeScreen.newMessageAccountIDInput.selector, + ); + await expect(messageButton).toHaveClass(/disabled/); + }, +); diff --git a/tests/automation/types/testing.ts b/tests/automation/types/testing.ts index 440abd7..a2cad1d 100644 --- a/tests/automation/types/testing.ts +++ b/tests/automation/types/testing.ts @@ -120,6 +120,7 @@ export type DataTestId = | 'edit-profile-icon' | 'empty-conversation-notification' | 'enable-calls-settings-row' + | 'enable-communities-message-requests-settings-row' | 'enable-microphone-settings-row' | 'enable-read-receipts-settings-row' | 'end-call' diff --git a/tests/automation/utilities/utils.ts b/tests/automation/utilities/utils.ts index 62df7da..78e53ad 100644 --- a/tests/automation/utilities/utils.ts +++ b/tests/automation/utilities/utils.ts @@ -373,7 +373,7 @@ export async function typeIntoInput( window: Page, dataTestId: DataTestId, text: string, - paste?: boolean, // typing long messages hits the runner timeout + paste?: boolean, // typing long messages hits the runner timeout ) { console.info(`typeIntoInput testId: ${dataTestId} : "${text}"`); const builtSelector = `css=[data-testid=${dataTestId}]`; @@ -384,9 +384,9 @@ export async function typeIntoInput( // reset the content to be empty before typing into the input await window.fill(builtSelector, ''); if (paste) { - await window.fill(builtSelector, text); + await window.fill(builtSelector, text); } else { - await window.type(builtSelector, text); + await window.type(builtSelector, text); } } From 8d54f53d8b3e6f3e554937c618c30fa16ff2952d Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 20 Oct 2025 13:44:03 +1100 Subject: [PATCH 3/3] fix: use prebuilt state for length checks --- tests/automation/message_checks.spec.ts | 21 ++++++++------------- tests/automation/message_requests.spec.ts | 4 ++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/tests/automation/message_checks.spec.ts b/tests/automation/message_checks.spec.ts index ab12ef9..af402d1 100644 --- a/tests/automation/message_checks.spec.ts +++ b/tests/automation/message_checks.spec.ts @@ -10,7 +10,6 @@ import { } from './locators'; import { newUser } from './setup/new_user'; import { - sessionTestOneWindow, sessionTestTwoWindows, test_Alice_1W_Bob_1W, } from './setup/sessionTest'; @@ -286,23 +285,15 @@ const messageLengthTestCases = [ ]; messageLengthTestCases.forEach((testCase) => { - sessionTestOneWindow( + test_Alice_1W_Bob_1W( `Message length limit (${testCase.length} chars)`, - async ([aliceWindow1]) => { - const alice = await newUser(aliceWindow1, 'Alice', false); + async ({ alice, aliceWindow1, bob, bobWindow1 }) => { + await createContact(aliceWindow1, bobWindow1, alice, bob); const expectedCount = testCase.length < countdownThreshold ? null : (maxChars - testCase.length).toString(); const message = testCase.char.repeat(testCase.length); - await clickOn(aliceWindow1, HomeScreen.plusButton); - await clickOn(aliceWindow1, HomeScreen.newMessageOption); - await typeIntoInput( - aliceWindow1, - 'new-session-conversation', - alice.accountid, - ); - await clickOn(aliceWindow1, HomeScreen.newMessageNextButton); // Type the message await typeIntoInput( aliceWindow1, @@ -341,7 +332,11 @@ messageLengthTestCases.forEach((testCase) => { if (testCase.shouldSend) { // Message should appear in Alice's window - await waitForTextMessage(aliceWindow1, message); + await Promise.all( + [aliceWindow1, bobWindow1].map(async (w) => { + await waitForTextMessage(w, message); + }), + ); } else { // Message Too Long modal await checkModalStrings( diff --git a/tests/automation/message_requests.spec.ts b/tests/automation/message_requests.spec.ts index 9ac4297..16bb2b4 100644 --- a/tests/automation/message_requests.spec.ts +++ b/tests/automation/message_requests.spec.ts @@ -199,7 +199,7 @@ test_Alice_1W_Bob_1W( bob.userName, ); const messageRequestMsg = `${alice.userName} to ${bob.userName}`; - const testReply = `${bob.userName} accepts message request`; + const messageRequestResponse = `${bob.userName} accepts message request`; await sendMessage(aliceWindow1, messageRequestMsg); await clickOn(bobWindow1, HomeScreen.messageRequestBanner); // Select message request from User A @@ -208,7 +208,7 @@ test_Alice_1W_Bob_1W( HomeScreen.conversationItemName, alice.userName, ); - await sendMessage(bobWindow1, testReply); + await sendMessage(bobWindow1, messageRequestResponse); // Check config message of message request acceptance await waitForTestIdWithText( bobWindow1,