diff --git a/run/localizer/constants.ts b/run/localizer/constants.ts index 6fd2b45b0..dd2717db3 100644 --- a/run/localizer/constants.ts +++ b/run/localizer/constants.ts @@ -9,14 +9,19 @@ export enum LOCALE_DEFAULTS { token_name_short = 'SESH', usd_name_short = 'USD', app_pro = 'Session Pro', + session_foundation = 'Session Foundation', + pro = 'Pro', } export const rtlLocales = ['ar', 'fa', 'he', 'ps', 'ur']; -export const crowdinLocales = ['en'] as const; +export const crowdinLocales = [ + 'en', +] as const; export type CrowdinLocale = (typeof crowdinLocales)[number]; export function isCrowdinLocale(locale: string): locale is CrowdinLocale { return crowdinLocales.includes(locale as CrowdinLocale); } + diff --git a/run/localizer/locales.ts b/run/localizer/locales.ts index 94b497a5b..17397f859 100644 --- a/run/localizer/locales.ts +++ b/run/localizer/locales.ts @@ -41,7 +41,7 @@ type WithStoreVariant = {storevariant: string}; type WithMin = {min: string}; type WithMax = {max: string}; -export type TokenSimpleNoArgs = +export type TokenSimpleNoArgs = 'about' | 'accept' | 'accountIDCopy' | @@ -81,12 +81,15 @@ export type TokenSimpleNoArgs = 'appIconEnableIconAndName' | 'appIconSelect' | 'appIconSelectionTitle' | + 'appName' | 'appNameCalculator' | 'appNameMeetingSE' | 'appNameNews' | 'appNameNotes' | 'appNameStocks' | 'appNameWeather' | + 'appPro' | + 'appProBadge' | 'appearanceAutoDarkMode' | 'appearanceHideMenuBar' | 'appearanceLanguage' | @@ -198,6 +201,7 @@ export type TokenSimpleNoArgs = 'cameraGrantAccessDescription' | 'cameraGrantAccessQr' | 'cancel' | + 'cancelPlan' | 'change' | 'changePasswordFail' | 'changePasswordModalDescription' | @@ -286,6 +290,8 @@ export type TokenSimpleNoArgs = 'copy' | 'create' | 'creatingCall' | + 'currentPassword' | + 'currentPlan' | 'cut' | 'darkMode' | 'databaseErrorClearDataWarning' | @@ -449,6 +455,7 @@ export type TokenSimpleNoArgs = 'hideOthers' | 'image' | 'images' | + 'important' | 'incognitoKeyboard' | 'incognitoKeyboardDescription' | 'info' | @@ -472,6 +479,7 @@ export type TokenSimpleNoArgs = 'linkPreviewsSendModalDescription' | 'linkPreviewsTurnedOff' | 'linkPreviewsTurnedOffDescription' | + 'links' | 'loadAccount' | 'loadAccountProgressMessage' | 'loading' | @@ -484,7 +492,9 @@ export type TokenSimpleNoArgs = 'lockAppStatus' | 'lockAppUnlock' | 'lockAppUnlocked' | + 'logs' | 'manageMembers' | + 'managePro' | 'max' | 'media' | 'membersAddAccountIdOrOns' | @@ -534,7 +544,10 @@ export type TokenSimpleNoArgs = 'modalMessageCharacterDisplayTitle' | 'modalMessageCharacterTooLongTitle' | 'modalMessageTooLongTitle' | + 'networkName' | + 'newPassword' | 'next' | + 'nextSteps' | 'nicknameEnter' | 'nicknameErrorShorter' | 'nicknameRemove' | @@ -607,6 +620,7 @@ export type TokenSimpleNoArgs = 'open' | 'openSurvey' | 'other' | + 'oxenFoundation' | 'password' | 'passwordChange' | 'passwordChangeShortDescription' | @@ -630,8 +644,8 @@ export type TokenSimpleNoArgs = 'passwordSetShortDescription' | 'passwordStrengthCharLength' | 'passwordStrengthIncludeNumber' | - 'passwordStrengthIncludesLetter' | 'passwordStrengthIncludesLowercase' | + 'passwordStrengthIncludesSymbol' | 'passwordStrengthIncludesUppercase' | 'passwordStrengthIndicator' | 'passwordStrengthIndicatorDescription' | @@ -675,31 +689,68 @@ export type TokenSimpleNoArgs = 'pinConversation' | 'pinUnpin' | 'pinUnpinConversation' | + 'plusLoadsMore' | 'preferences' | 'preview' | 'previewNotification' | + 'pro' | 'proActivated' | + 'proAllSet' | 'proAlreadyPurchased' | 'proAnimatedDisplayPicture' | 'proAnimatedDisplayPictureCallToActionDescription' | 'proAnimatedDisplayPictureFeature' | 'proAnimatedDisplayPictureModalDescription' | + 'proAnimatedDisplayPictures' | + 'proAnimatedDisplayPicturesDescription' | 'proAnimatedDisplayPicturesNonProModalDescription' | 'proBadge' | + 'proBadgeVisible' | + 'proBadges' | + 'proBadgesDescription' | 'proCallToActionLongerMessages' | 'proCallToActionPinnedConversations' | 'proCallToActionPinnedConversationsMoreThan' | + 'proExpired' | + 'proExpiredDescription' | + 'proExpiringSoon' | + 'proFaq' | + 'proFaqDescription' | 'proFeatureListAnimatedDisplayPicture' | 'proFeatureListLargerGroups' | 'proFeatureListLoadsMore' | 'proFeatureListLongerMessages' | 'proFeatureListPinnedConversations' | + 'proFeatures' | 'proGroupActivated' | 'proGroupActivatedDescription' | + 'proImportantDescription' | 'proIncreasedAttachmentSizeFeature' | 'proIncreasedMessageLengthFeature' | + 'proLargerGroups' | + 'proLargerGroupsDescription' | + 'proLongerMessages' | + 'proLongerMessagesDescription' | 'proMessageInfoFeatures' | + 'proPlanNotFound' | + 'proPlanNotFoundDescription' | + 'proPlanRecover' | + 'proPlanRenew' | + 'proPlanRenewStart' | + 'proPlanRenewSupport' | + 'proPlanRestored' | + 'proPlanRestoredDescription' | + 'proRefundDescription' | + 'proRefundRequestSessionSupport' | + 'proRefunding' | + 'proRequestedRefund' | 'proSendMore' | + 'proSettings' | + 'proStats' | + 'proStatsTooltip' | + 'proSupportDescription' | + 'proUnlimitedPins' | + 'proUnlimitedPinsDescription' | 'proUserProfileModalCallToAction' | 'profile' | 'profileDisplayPicture' | @@ -750,7 +801,9 @@ export type TokenSimpleNoArgs = 'remove' | 'removePasswordFail' | 'removePasswordModalDescription' | + 'renew' | 'reply' | + 'requestRefund' | 'resend' | 'resolving' | 'restart' | @@ -784,6 +837,8 @@ export type TokenSimpleNoArgs = 'sessionAppearance' | 'sessionClearData' | 'sessionConversations' | + 'sessionDownloadUrl' | + 'sessionFoundation' | 'sessionHelp' | 'sessionInviteAFriend' | 'sessionMessageRequests' | @@ -799,6 +854,7 @@ export type TokenSimpleNoArgs = 'sessionNotifications' | 'sessionPermissions' | 'sessionPrivacy' | + 'sessionProBeta' | 'sessionRecoveryPassword' | 'sessionSettings' | 'set' | @@ -817,6 +873,7 @@ export type TokenSimpleNoArgs = 'showNoteToSelf' | 'showNoteToSelfDescription' | 'spellChecker' | + 'stakingRewardPool' | 'stickers' | 'strength' | 'supportDescription' | @@ -825,7 +882,10 @@ export type TokenSimpleNoArgs = 'theContinue' | 'theDefault' | 'theError' | + 'theReturn' | 'themePreview' | + 'tokenNameLong' | + 'tokenNameShort' | 'tooltipBlindedIdCommunities' | 'translate' | 'tray' | @@ -847,6 +907,8 @@ export type TokenSimpleNoArgs = 'updateGroupInformationDescription' | 'updateGroupInformationEnterShorterDescription' | 'updateNewVersion' | + 'updatePlan' | + 'updatePlanTwo' | 'updateProfileInformation' | 'updateProfileInformationDescription' | 'updateReleaseNotes' | @@ -858,6 +920,8 @@ export type TokenSimpleNoArgs = 'urlCopy' | 'urlOpen' | 'urlOpenBrowser' | + 'urlOpenDescriptionAlternative' | + 'usdNameShort' | 'useFastMode' | 'video' | 'videoErrorPlay' | @@ -1018,13 +1082,52 @@ export type TokensSimpleAndArgs = { notificationsMutedFor: WithTimeLarge, notificationsMutedForTime: WithDateTime, notificationsSystem: WithMessageCount & WithConversationCount, + onDevice: { device_type: string }, + onDeviceDescription: { device_type: string, platform_account: string }, onboardingBubbleCreatingAnAccountIsEasy: WithEmoji, onboardingBubbleWelcomeToSession: WithEmoji, + openStoreWebsite: { platform_store: string }, passwordErrorLength: WithMin & WithMax, + plusLoadsMoreDescription: WithIcon, + proAllSetDescription: WithDate, + proAutoRenewTime: WithTime, + proBadgesSent: WithCount, + proBilledAnnually: { price: string }, + proBilledMonthly: { price: string }, + proBilledQuarterly: { price: string }, + proDiscountTooltip: { percent: string }, + proExpiringSoonDescription: WithTime, + proGroupsUpgraded: WithCount, + proLongerMessagesSent: WithCount, + proPercentOff: { percent: string }, + proPinnedConversations: WithCount, + proPlanActivatedAuto: WithDate & { current_plan: string }, + proPlanActivatedAutoShort: WithDate & { current_plan: string }, + proPlanActivatedNotAuto: WithDate, + proPlanExpireDate: WithDate, + proPlanPlatformRefund: { platform_store: string, platform_account: string }, + proPlanPlatformRefundLong: { platform_store: string }, + proPlanRenewDesktop: { platform_store: string }, + proPlanRenewDesktopLinked: { platform_store: string }, + proPlanRenewDesktopStore: { platform_store: string, platform_account: string }, + proPlanSignUp: { platform_store: string, platform_account: string }, + proPriceOneMonth: { monthly_price: string }, + proPriceThreeMonths: { monthly_price: string }, + proPriceTwelveMonths: { monthly_price: string }, + proRefundNextSteps: { platform_account: string }, + proRefundRequestStorePolicies: { platform_account: string }, + proRefundSupport: { platform_account: string, platform_store: string }, + proRefundingDescription: { platform_account: string, platform_store: string }, + proTosPrivacy: WithIcon, + proUpdatePlanDescription: WithDate & { current_plan: string, selected_plan: string }, + proUpdatePlanExpireDescription: WithDate & { selected_plan: string }, + processingRefundRequest: { platform_account: string }, rateSessionModalDescription: WithStoreVariant, + refundPlanNonOriginatorApple: { platform_account: string }, remainingCharactersOverTooltip: WithCount, screenshotTaken: WithName, searchMatchesNoneSpecific: WithQuery, + sessionNetworkDataPrice: WithDateTime, sessionNetworkDescription: WithIcon, systemInformationDesktop: WithInformation, tooltipAccountIdVisible: WithName, @@ -1033,7 +1136,8 @@ export type TokensSimpleAndArgs = { updateVersion: WithVersion, updated: WithRelativeTime, urlOpenDescription: WithUrl, - sessionNetworkDataPrice: WithDateTime + viaStoreWebsite: { platform_store: string }, + viaStoreWebsiteDescription: { platform_account: string, platform_store: string } }; export type TokensPluralAndArgs = { @@ -1065,7 +1169,7 @@ export type TokensPluralAndArgs = { searchMatches: WithFoundCount & WithCount }; -export type TokenSimpleWithArgs = +export type TokenSimpleWithArgs = 'accountIdShare' | 'adminMorePromotedToAdmin' | 'adminPromoteDescription' | @@ -1209,13 +1313,52 @@ export type TokenSimpleWithArgs = 'notificationsMutedFor' | 'notificationsMutedForTime' | 'notificationsSystem' | + 'onDevice' | + 'onDeviceDescription' | 'onboardingBubbleCreatingAnAccountIsEasy' | 'onboardingBubbleWelcomeToSession' | + 'openStoreWebsite' | 'passwordErrorLength' | + 'plusLoadsMoreDescription' | + 'proAllSetDescription' | + 'proAutoRenewTime' | + 'proBadgesSent' | + 'proBilledAnnually' | + 'proBilledMonthly' | + 'proBilledQuarterly' | + 'proDiscountTooltip' | + 'proExpiringSoonDescription' | + 'proGroupsUpgraded' | + 'proLongerMessagesSent' | + 'proPercentOff' | + 'proPinnedConversations' | + 'proPlanActivatedAuto' | + 'proPlanActivatedAutoShort' | + 'proPlanActivatedNotAuto' | + 'proPlanExpireDate' | + 'proPlanPlatformRefund' | + 'proPlanPlatformRefundLong' | + 'proPlanRenewDesktop' | + 'proPlanRenewDesktopLinked' | + 'proPlanRenewDesktopStore' | + 'proPlanSignUp' | + 'proPriceOneMonth' | + 'proPriceThreeMonths' | + 'proPriceTwelveMonths' | + 'proRefundNextSteps' | + 'proRefundRequestStorePolicies' | + 'proRefundSupport' | + 'proRefundingDescription' | + 'proTosPrivacy' | + 'proUpdatePlanDescription' | + 'proUpdatePlanExpireDescription' | + 'processingRefundRequest' | 'rateSessionModalDescription' | + 'refundPlanNonOriginatorApple' | 'remainingCharactersOverTooltip' | 'screenshotTaken' | 'searchMatchesNoneSpecific' | + 'sessionNetworkDataPrice' | 'sessionNetworkDescription' | 'systemInformationDesktop' | 'tooltipAccountIdVisible' | @@ -1224,9 +1367,10 @@ export type TokenSimpleWithArgs = 'updateVersion' | 'updated' | 'urlOpenDescription' | - 'sessionNetworkDataPrice' + 'viaStoreWebsite' | + 'viaStoreWebsiteDescription' -export type TokenPluralWithArgs = +export type TokenPluralWithArgs = 'adminSendingPromotion' | 'clearDataErrorDescription' | 'deleteMessage' | @@ -1375,6 +1519,9 @@ export const simpleDictionaryNoArgs: Record< appIconSelectionTitle: { en: "Icon", }, + appName: { + en: "Session", + }, appNameCalculator: { en: "Calculator", }, @@ -1393,6 +1540,12 @@ export const simpleDictionaryNoArgs: Record< appNameWeather: { en: "Weather", }, + appPro: { + en: "Session Pro", + }, + appProBadge: { + en: "Session Pro Badge", + }, appearanceAutoDarkMode: { en: "Auto Dark Mode", }, @@ -1700,7 +1853,7 @@ export const simpleDictionaryNoArgs: Record< en: "Voice and Video Calls (Beta)", }, callsVoiceAndVideoModalDescription: { - en: "Your IP is visible to your call partner and a Session Technology Foundation server while using beta calls.", + en: "Your IP is visible to your call partner and a Session Foundation server while using beta calls.", }, callsVoiceAndVideoToggleDescription: { en: "Enables voice and video calls to and from other users.", @@ -1726,6 +1879,9 @@ export const simpleDictionaryNoArgs: Record< cancel: { en: "Cancel", }, + cancelPlan: { + en: "Cancel Plan", + }, change: { en: "Change", }, @@ -1990,6 +2146,12 @@ export const simpleDictionaryNoArgs: Record< creatingCall: { en: "Creating Call", }, + currentPassword: { + en: "Current Password", + }, + currentPlan: { + en: "Current Plan", + }, cut: { en: "Cut", }, @@ -2479,6 +2641,9 @@ export const simpleDictionaryNoArgs: Record< images: { en: "images", }, + important: { + en: "Important", + }, incognitoKeyboard: { en: "Incognito Keyboard", }, @@ -2548,6 +2713,9 @@ export const simpleDictionaryNoArgs: Record< linkPreviewsTurnedOffDescription: { en: "Session must contact linked websites to generate previews of links you send and receive.

You can turn them on in Session's settings.", }, + links: { + en: "Links", + }, loadAccount: { en: "Load Account", }, @@ -2584,9 +2752,15 @@ export const simpleDictionaryNoArgs: Record< lockAppUnlocked: { en: "Session is unlocked", }, + logs: { + en: "Logs", + }, manageMembers: { en: "Manage Members", }, + managePro: { + en: "Manage Pro", + }, max: { en: "Max", }, @@ -2734,9 +2908,18 @@ export const simpleDictionaryNoArgs: Record< modalMessageTooLongTitle: { en: "Message Too Long", }, + networkName: { + en: "Session Network", + }, + newPassword: { + en: "New Password", + }, next: { en: "Next", }, + nextSteps: { + en: "Next Steps", + }, nicknameEnter: { en: "Enter nickname", }, @@ -2953,6 +3136,9 @@ export const simpleDictionaryNoArgs: Record< other: { en: "Other", }, + oxenFoundation: { + en: "Oxen Foundation", + }, password: { en: "Password", }, @@ -3022,12 +3208,12 @@ export const simpleDictionaryNoArgs: Record< passwordStrengthIncludeNumber: { en: "Includes a number", }, - passwordStrengthIncludesLetter: { - en: "Includes a letter", - }, passwordStrengthIncludesLowercase: { en: "Includes a lowercase letter", }, + passwordStrengthIncludesSymbol: { + en: "Includes a symbol", + }, passwordStrengthIncludesUppercase: { en: "Includes a uppercase letter", }, @@ -3157,6 +3343,9 @@ export const simpleDictionaryNoArgs: Record< pinUnpinConversation: { en: "Unpin Conversation", }, + plusLoadsMore: { + en: "Plus Loads More...", + }, preferences: { en: "Preferences", }, @@ -3166,9 +3355,15 @@ export const simpleDictionaryNoArgs: Record< previewNotification: { en: "Preview Notification", }, + pro: { + en: "Pro", + }, proActivated: { en: "Activated", }, + proAllSet: { + en: "You're all set!", + }, proAlreadyPurchased: { en: "You’ve already got", }, @@ -3184,11 +3379,26 @@ export const simpleDictionaryNoArgs: Record< proAnimatedDisplayPictureModalDescription: { en: "users can upload GIFs", }, + proAnimatedDisplayPictures: { + en: "Animated Display Pictures", + }, + proAnimatedDisplayPicturesDescription: { + en: "Set animated GIFs and WebP images as your display picture.", + }, proAnimatedDisplayPicturesNonProModalDescription: { en: "Upload GIFs with", }, proBadge: { - en: "Session Pro Badge", + en: "Pro Badge", + }, + proBadgeVisible: { + en: "Show Session Pro badge to other users", + }, + proBadges: { + en: "Badges", + }, + proBadgesDescription: { + en: "Show your support for Session with an exclusive badge next to your display name.", }, proCallToActionLongerMessages: { en: "Want to send longer messages? Send more text and unlock premium features with Session Pro", @@ -3199,6 +3409,21 @@ export const simpleDictionaryNoArgs: Record< proCallToActionPinnedConversationsMoreThan: { en: "Want more than 5 pins? Organize your chats and unlock premium features with Session Pro", }, + proExpired: { + en: "Expired", + }, + proExpiredDescription: { + en: "Unfortunately, your Pro plan has expired. Renew to keep accessing the exclusive perks and features of Session Pro.", + }, + proExpiringSoon: { + en: "Expiring Soon", + }, + proFaq: { + en: "Pro FAQ", + }, + proFaqDescription: { + en: "Find answers to common questions in the Session FAQ.", + }, proFeatureListAnimatedDisplayPicture: { en: "Upload GIF and WebP display pictures", }, @@ -3214,24 +3439,96 @@ export const simpleDictionaryNoArgs: Record< proFeatureListPinnedConversations: { en: "Pin unlimited conversations", }, + proFeatures: { + en: "Pro Features", + }, proGroupActivated: { en: "Group Activated", }, proGroupActivatedDescription: { en: "This group has expanded capacity! It can support up to 300 members because a group admin has", }, + proImportantDescription: { + en: "Requesting a refund is final. If approved, your Pro plan will be canceled immediately and you will lose access to all Pro features.", + }, proIncreasedAttachmentSizeFeature: { en: "Increased Attachment Size", }, proIncreasedMessageLengthFeature: { en: "Increased Message Length", }, + proLargerGroups: { + en: "Larger Groups", + }, + proLargerGroupsDescription: { + en: "Groups you are an admin in are automatically upgraded to support 300 members.", + }, + proLongerMessages: { + en: "Longer Messages", + }, + proLongerMessagesDescription: { + en: "You can send messages up to 10,000 characters in all conversations.", + }, proMessageInfoFeatures: { en: "This message used the following Session Pro features:", }, + proPlanNotFound: { + en: "Pro Plan Not Found", + }, + proPlanNotFoundDescription: { + en: "No active plan was found for your account. If you believe this is a mistake, please reach out to Session support for assistance.", + }, + proPlanRecover: { + en: "Recover Pro Plan", + }, + proPlanRenew: { + en: "Renew Pro Plan", + }, + proPlanRenewStart: { + en: "Renew your Session Pro plan to start using powerful Session Pro features again.", + }, + proPlanRenewSupport: { + en: "Your Session Pro plan has been renewed! Thank you for supporting the Session Network.", + }, + proPlanRestored: { + en: "Pro Plan Restored", + }, + proPlanRestoredDescription: { + en: "A valid plan for Session Pro was detected and your Pro status has been restored!", + }, + proRefundDescription: { + en: "We’re sorry to see you go. Here's what you need to know before requesting a refund.", + }, + proRefundRequestSessionSupport: { + en: "Your refund request will be handled by Session Support.

Request a refund by hitting the button below and completing the refund request form.

While Session Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume.", + }, + proRefunding: { + en: "Refunding Pro", + }, + proRequestedRefund: { + en: "Refund Requested", + }, proSendMore: { en: "Send more with", }, + proSettings: { + en: "Pro Settings", + }, + proStats: { + en: "Your Pro Stats", + }, + proStatsTooltip: { + en: "Pro stats reflect usage on this device and may appear differently on linked devices", + }, + proSupportDescription: { + en: "Need help with your Pro plan? Submit a request to the support team.", + }, + proUnlimitedPins: { + en: "Unlimited Pins", + }, + proUnlimitedPinsDescription: { + en: "Organize all your chats with unlimited pinned conversations.", + }, proUserProfileModalCallToAction: { en: "Want to get more out of Session? Upgrade to Session Pro for a more powerful messaging experience.", }, @@ -3382,9 +3679,15 @@ export const simpleDictionaryNoArgs: Record< removePasswordModalDescription: { en: "Remove your current password for Session. Locally stored data will be re-encrypted with a randomly generated key, stored on your device.", }, + renew: { + en: "Renew", + }, reply: { en: "Reply", }, + requestRefund: { + en: "Request Refund", + }, resend: { en: "Resend", }, @@ -3484,6 +3787,12 @@ export const simpleDictionaryNoArgs: Record< sessionConversations: { en: "Conversations", }, + sessionDownloadUrl: { + en: "https://getsession.org/download", + }, + sessionFoundation: { + en: "Session Foundation", + }, sessionHelp: { en: "Help", }, @@ -3529,6 +3838,9 @@ export const simpleDictionaryNoArgs: Record< sessionPrivacy: { en: "Privacy", }, + sessionProBeta: { + en: "Session Pro Beta", + }, sessionRecoveryPassword: { en: "Recovery Password", }, @@ -3583,6 +3895,9 @@ export const simpleDictionaryNoArgs: Record< spellChecker: { en: "Spell Checker", }, + stakingRewardPool: { + en: "Staking Reward Pool", + }, stickers: { en: "Stickers", }, @@ -3607,9 +3922,18 @@ export const simpleDictionaryNoArgs: Record< theError: { en: "Error", }, + theReturn: { + en: "Return", + }, themePreview: { en: "Theme Preview", }, + tokenNameLong: { + en: "Session Token", + }, + tokenNameShort: { + en: "SESH", + }, tooltipBlindedIdCommunities: { en: "Blinded IDs are used in communities to reduce spam and increase privacy", }, @@ -3673,6 +3997,12 @@ export const simpleDictionaryNoArgs: Record< updateNewVersion: { en: "A new version of Session is available, tap to update", }, + updatePlan: { + en: "Update Plan", + }, + updatePlanTwo: { + en: "Two ways to update your plan:", + }, updateProfileInformation: { en: "Update Profile Information", }, @@ -3706,6 +4036,12 @@ export const simpleDictionaryNoArgs: Record< urlOpenBrowser: { en: "This will open in your browser.", }, + urlOpenDescriptionAlternative: { + en: "Links will open in your browser.", + }, + usdNameShort: { + en: "USD", + }, useFastMode: { en: "Use Fast Mode", }, @@ -4186,18 +4522,132 @@ export const simpleDictionaryWithArgs: Record< }, notificationsSystem: { en: "{message_count} new messages in {conversation_count} conversations", + }, + onDevice: { + en: "On your {device_type} device", + }, + onDeviceDescription: { + en: "Open this Session account on an {device_type} device logged into the {platform_account} you originally signed up with. Then, change your plan via the Session Pro settings.", }, onboardingBubbleCreatingAnAccountIsEasy: { en: "Creating an account is instant, free, and anonymous {emoji}", }, onboardingBubbleWelcomeToSession: { en: "Welcome to Session {emoji}", + }, + openStoreWebsite: { + en: "Open {platform_store} Website", }, passwordErrorLength: { en: "Password must be between {min} and {max} characters long", + }, + plusLoadsMoreDescription: { + en: "New features coming soon to Pro. Discover what's next on the Pro Roadmap {icon}", + }, + proAllSetDescription: { + en: "Your Session Pro plan was updated! You will be billed when your current Pro plan is automatically renewed on {date}.", + }, + proAutoRenewTime: { + en: "Pro auto-renewing in {time}", + }, + proBadgesSent: { + en: "{count} Pro Badges Sent", + }, + proBilledAnnually: { + en: "{price} Billed Annually", + }, + proBilledMonthly: { + en: "{price} Billed Monthly", + }, + proBilledQuarterly: { + en: "{price} Billed Quarterly", + }, + proDiscountTooltip: { + en: "Your current plan is already discounted by{percent}% of the full Session Pro price.", + }, + proExpiringSoonDescription: { + en: "Your Pro plan is expiring in {time}. Update your plan to keep accessing the exclusive perks and features of Session Pro.", + }, + proGroupsUpgraded: { + en: "{count} Groups Upgraded", + }, + proLongerMessagesSent: { + en: "{count} Longer Messages Sent", + }, + proPercentOff: { + en: "{percent}% Off", + }, + proPinnedConversations: { + en: "{count} Pinned Conversations", + }, + proPlanActivatedAuto: { + en: "Your Session Pro plan is active!

Your plan will automatically renew for another {current_plan} on {date}. Updates to your plan take effect when Pro is next renewed.", + }, + proPlanActivatedAutoShort: { + en: "Your Session Pro plan is active!

Your plan will automatically renew for another {current_plan} on {date}.", + }, + proPlanActivatedNotAuto: { + en: "Your Session Pro plan will expire on {date}.

Update your plan now to ensure uninterrupted access to exclusive Pro features.", + }, + proPlanExpireDate: { + en: "Your Session Pro plan will expire on {date}.", + }, + proPlanPlatformRefund: { + en: "Because you originally signed up for Session Pro via the {platform_store} Store, you'll need to use the same {platform_account} to request a refund.", + }, + proPlanPlatformRefundLong: { + en: "Because you originally signed up for Session Pro via the {platform_store} Store, your refund request will be processed by Session Support.

Request a refund by hitting the button below and completing the refund request form.

While Session Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume.", + }, + proPlanRenewDesktop: { + en: "Currently, Pro plans can only be purchased and renewed via the {platform_store} or {platform_store} Stores. Because you are using Session Desktop, you're not able to renew your plan here.

Session Pro developers are working hard on alternative payment options to allow users to purchase Pro plans outside of the {platform_store} and {platform_store} Stores. Pro Roadmap", + }, + proPlanRenewDesktopLinked: { + en: "Renew your plan in the Session Pro settings on a linked device with Session installed via the {platform_store} or {platform_store} Store.", + }, + proPlanRenewDesktopStore: { + en: "Renew your plan on the {platform_store} website using the {platform_account} you signed up for Pro with.", + }, + proPlanSignUp: { + en: "Because you originally signed up for Session Pro via the {platform_store} Store, you'll need to use your {platform_account} to update your plan.", + }, + proPriceOneMonth: { + en: "1 Month - {monthly_price} / Month", + }, + proPriceThreeMonths: { + en: "3 Months - {monthly_price} / Month", + }, + proPriceTwelveMonths: { + en: "12 Months - {monthly_price} / Month", + }, + proRefundNextSteps: { + en: "{platform_account} is now processing your refund request. This typically takes 24-48 hours. Depending on their decision, you may see your Pro status change in Session.", + }, + proRefundRequestStorePolicies: { + en: "Your refund request will be handled exclusively by {platform_account} through the {platform_account} website.

Due to {platform_account} refund policies, Session developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued.", + }, + proRefundSupport: { + en: "Please contact {platform_account} for further updates on your refund request. Due to {platform_account} refund policies, Session developers have no ability to influence the outcome of refund requests.

{platform_store} Refund Support", + }, + proRefundingDescription: { + en: "Refunds for Session Pro plans are handled exclusively by {platform_account} through the {platform_store} Store.

Due to {platform_account} refund policies, Session developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued.", + }, + proTosPrivacy: { + en: "By updating, you agree to the Session Pro Terms of Service {icon} and Privacy Policy {icon}", + }, + proUpdatePlanDescription: { + en: "You are currently on the {current_plan} Plan. Are you sure you want to switch to the {selected_plan} Plan?

By updating, your plan will automatically renew on {date} for an additional {selected_plan} of Pro access.", + }, + proUpdatePlanExpireDescription: { + en: "Your plan will expire on {date}.

By updating, your plan will automatically renew on {date} for an additional {selected_plan} of Pro access.", + }, + processingRefundRequest: { + en: "{platform_account} is processing your refund request", }, rateSessionModalDescription: { en: "We're glad you're enjoying Session, if you have a moment, rating us in the {storevariant} helps others discover private, secure messaging!", + }, + refundPlanNonOriginatorApple: { + en: "Because you originally signed up for Session Pro via a different {platform_account}, you'll need to use that {platform_account} to update your plan.", }, remainingCharactersOverTooltip: { en: "Reduce message length by {count}", @@ -4207,6 +4657,9 @@ export const simpleDictionaryWithArgs: Record< }, searchMatchesNoneSpecific: { en: "No results found for {query}", + }, + sessionNetworkDataPrice: { + en: "Price data powered by CoinGecko
Accurate at {date_time}", }, sessionNetworkDescription: { en: "Messages are sent using the Session Network. The network is comprised of nodes incentivized with Session Token, which keeps Session decentralized and secure. Learn More {icon}", @@ -4232,8 +4685,11 @@ export const simpleDictionaryWithArgs: Record< urlOpenDescription: { en: "Are you sure you want to open this URL in your browser?

{url}", }, - sessionNetworkDataPrice: { - en: "Price data powered by CoinGecko
Accurate at {date_time}", + viaStoreWebsite: { + en: "Via the {platform_store} website", + }, + viaStoreWebsiteDescription: { + en: "Change your plan using the {platform_account} you used to sign up with, via the {platform_store} website.", }, } as const; diff --git a/run/test/specs/app_disguise_icons.spec.ts b/run/test/specs/app_disguise_icons.spec.ts index 5ce75ee21..312c9a5d2 100644 --- a/run/test/specs/app_disguise_icons.spec.ts +++ b/run/test/specs/app_disguise_icons.spec.ts @@ -1,9 +1,9 @@ -import type { TestInfo } from '@playwright/test'; +import { test, type TestInfo } from '@playwright/test'; +import { TestSteps } from '../../types/allure'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; import { AppearanceMenuItem, SelectAppIcon, UserSettings } from './locators/settings'; -import { sleepFor } from './utils'; import { newUser } from './utils/create_account'; import { closeApp, openAppOnPlatformSingleDevice, SupportedPlatformsType } from './utils/open_app'; import { AppDisguisePageScreenshot } from './utils/screenshot_paths'; @@ -22,16 +22,20 @@ bothPlatformsIt({ }); async function appDisguiseIcons(platform: SupportedPlatformsType, testInfo: TestInfo) { - const { device } = await openAppOnPlatformSingleDevice(platform, testInfo); - await newUser(device, USERNAME.ALICE, { saveUserData: false }); - await device.clickOnElementAll(new UserSettings(device)); - // Must scroll down to reveal the Appearance menu item - await device.scrollDown(); - await device.clickOnElementAll(new AppearanceMenuItem(device)); - await sleepFor(2000); - // Must scroll down to reveal the app disguise option - await device.scrollDown(); - await device.clickOnElementAll(new SelectAppIcon(device)); - await verifyElementScreenshot(device, new AppDisguisePageScreenshot(device), testInfo); - await closeApp(device); + const { device } = await test.step(TestSteps.SETUP.NEW_USER, async () => { + const { device } = await openAppOnPlatformSingleDevice(platform, testInfo); + await newUser(device, USERNAME.ALICE, { saveUserData: false }); + return { device }; + }); + await test.step(TestSteps.OPEN.APPEARANCE, async () => { + await device.clickOnElementAll(new UserSettings(device)); + await device.clickOnElementAll(new AppearanceMenuItem(device)); + }); + await test.step(TestSteps.VERIFY.ELEMENT_SCREENSHOT('app disguise icons'), async () => { + await device.clickOnElementAll(new SelectAppIcon(device)); + await verifyElementScreenshot(device, new AppDisguisePageScreenshot(device), testInfo); + }); + await test.step(TestSteps.SETUP.CLOSE_APP, async () => { + await closeApp(device); + }); } diff --git a/run/test/specs/app_disguise_set.spec.ts b/run/test/specs/app_disguise_set.spec.ts index f01490f7f..15c7d3a84 100644 --- a/run/test/specs/app_disguise_set.spec.ts +++ b/run/test/specs/app_disguise_set.spec.ts @@ -1,7 +1,8 @@ -import type { TestInfo } from '@playwright/test'; +import { test, type TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; -import { androidIt } from '../../types/sessionIt'; +import { TestSteps } from '../../types/allure'; +import { bothPlatformsItSeparate } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; import { DisguisedApp } from './locators/external'; import { @@ -18,12 +19,16 @@ import { openAppOnPlatformSingleDevice, SupportedPlatformsType } from './utils/o import { closeApp } from './utils/open_app'; import { runScriptAndLog } from './utils/utilities'; -// iOS implementation blocked by SES-3809 -androidIt({ +bothPlatformsItSeparate({ title: 'App disguise set icon', risk: 'medium', countOfDevicesNeeded: 1, - testCb: appDisguiseSetIcon, + android: { + testCb: appDisguiseSetIconAndroid, + }, + ios: { + testCb: appDisguiseSetIconIOS, + }, allureSuites: { parent: 'Settings', suite: 'App Disguise', @@ -31,34 +36,80 @@ androidIt({ allureDescription: 'Verifies the alternate icon set on the App Disguise page is applied', }); -async function appDisguiseSetIcon(platform: SupportedPlatformsType, testInfo: TestInfo) { - const { device } = await openAppOnPlatformSingleDevice(platform, testInfo); - await newUser(device, USERNAME.ALICE, { saveUserData: false }); - await device.clickOnElementAll(new UserSettings(device)); - // Must scroll down to reveal the Appearance menu item - await device.scrollDown(); - await device.clickOnElementAll(new AppearanceMenuItem(device)); - await sleepFor(2000); - // Must scroll down to reveal the app disguise option - await device.scrollDown(); - await device.clickOnElementAll(new SelectAppIcon(device)); - try { - await device.clickOnElementAll(new AppDisguiseMeetingIcon(device)); - await device.checkModalStrings( - englishStrippedStr('appIconAndNameChange').toString(), - englishStrippedStr('appIconAndNameChangeConfirmation').toString() - ); - await device.clickOnElementAll(new CloseAppButton(device)); - await sleepFor(2000); - // Open app library and check for disguised app - await device.swipeFromBottom(); - await device.waitForTextElementToBePresent(new DisguisedApp(device)); - } finally { - // The disguised app must be uninstalled otherwise every following test will fail - await closeApp(device); - await runScriptAndLog( - `${getAdbFullPath()} -s ${device.udid} uninstall network.loki.messenger.qa`, - true - ); - } +async function appDisguiseSetIconIOS(platform: SupportedPlatformsType, testInfo: TestInfo) { + const { device } = await test.step(TestSteps.SETUP.NEW_USER, async () => { + const { device } = await openAppOnPlatformSingleDevice(platform, testInfo); + await newUser(device, USERNAME.ALICE, { saveUserData: false }); + return { device }; + }); + await test.step(TestSteps.OPEN.APPEARANCE, async () => { + await device.clickOnElementAll(new UserSettings(device)); + await device.clickOnElementAll(new AppearanceMenuItem(device)); + }); + await test.step(TestSteps.USER_ACTIONS.APP_DISGUISE, async () => { + await device.clickOnElementAll(new SelectAppIcon(device)); + try { + await device.clickOnElementAll(new AppDisguiseMeetingIcon(device)); + await test.step(TestSteps.VERIFY.SPECIFIC_MODAL('app disguise'), async () => { + await device.waitForTextElementToBePresent({ + strategy: 'accessibility id', + selector: 'You have changed the icon for “Session”.', + }); + await device.clickOnElementAll({ + strategy: 'accessibility id', + selector: 'OK', + }); + }); + // TODO maybe grab a screenshot of the disguised app and see what you can do with it + } finally { + // The disguised app must be uninstalled otherwise every following test will fail + await test.step(TestSteps.SETUP.CLOSE_APP, async () => { + await closeApp(device); + await runScriptAndLog( + `xcrun simctl uninstall ${device.udid} com.loki-project.loki-messenger`, + true + ); + }); + } + }); +} + +async function appDisguiseSetIconAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { + const { device } = await test.step(TestSteps.SETUP.NEW_USER, async () => { + const { device } = await openAppOnPlatformSingleDevice(platform, testInfo); + await newUser(device, USERNAME.ALICE, { saveUserData: false }); + return { device }; + }); + await test.step(TestSteps.OPEN.APPEARANCE, async () => { + await device.clickOnElementAll(new UserSettings(device)); + await device.clickOnElementAll(new AppearanceMenuItem(device)); + }); + await test.step(TestSteps.USER_ACTIONS.APP_DISGUISE, async () => { + await device.clickOnElementAll(new SelectAppIcon(device)); + try { + await device.clickOnElementAll(new AppDisguiseMeetingIcon(device)); + await test.step(TestSteps.VERIFY.SPECIFIC_MODAL('app disgusie'), async () => { + await device.checkModalStrings( + englishStrippedStr('appIconAndNameChange').toString(), + englishStrippedStr('appIconAndNameChangeConfirmation').toString() + ); + }); + await test.step('Verify app icon changed', async () => { + await device.clickOnElementAll(new CloseAppButton(device)); + await sleepFor(2000); + // Open app library and check for disguised app + await device.swipeFromBottom(); + await device.waitForTextElementToBePresent(new DisguisedApp(device)); + }); + } finally { + // The disguised app must be uninstalled otherwise every following test will fail + await test.step(TestSteps.SETUP.CLOSE_APP, async () => { + await closeApp(device); + await runScriptAndLog( + `${getAdbFullPath()} -s ${device.udid} uninstall network.loki.messenger.qa`, + true + ); + }); + } + }); } diff --git a/run/test/specs/check_avatar_color.spec.ts b/run/test/specs/check_avatar_color.spec.ts index aa97d488c..80dbbb1ae 100644 --- a/run/test/specs/check_avatar_color.spec.ts +++ b/run/test/specs/check_avatar_color.spec.ts @@ -4,7 +4,7 @@ import { TestSteps } from '../../types/allure'; import { bothPlatformsIt } from '../../types/sessionIt'; import { ConversationSettings } from './locators/conversation'; import { ConversationItem } from './locators/home'; -import { UserSettings } from './locators/settings'; +import { UserAvatar, UserSettings } from './locators/settings'; import { open_Alice1_Bob1_friends } from './state_builder'; import { isSameColor } from './utils/check_colour'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -35,7 +35,7 @@ async function avatarColor(platform: SupportedPlatformsType, testInfo: TestInfo) }); await test.step(`Get Alice's avatar color on their device from the Settings screen avatar`, async () => { await alice1.clickOnElementAll(new UserSettings(alice1)); - alice1PixelColor = await alice1.getElementPixelColor(new UserSettings(alice1)); + alice1PixelColor = await alice1.getElementPixelColor(new UserAvatar(alice1)); }); await test.step(`Get Alice's avatar color on bob's device from the Conversation Settings avatar`, async () => { await bob1.clickOnElementAll(new ConversationItem(bob1, alice.userName)); diff --git a/run/test/specs/community_emoji_react.spec.ts b/run/test/specs/community_emoji_react.spec.ts new file mode 100644 index 000000000..bcc656564 --- /dev/null +++ b/run/test/specs/community_emoji_react.spec.ts @@ -0,0 +1,61 @@ +import { test, type TestInfo } from '@playwright/test'; + +import { testCommunityLink, testCommunityName } from '../../constants/community'; +import { TestSteps } from '../../types/allure'; +import { bothPlatformsIt } from '../../types/sessionIt'; +import { EmojiReactsPill, FirstEmojiReact } from './locators/conversation'; +import { open_Alice1_Bob1_friends } from './state_builder'; +import { joinCommunity } from './utils/join_community'; +import { closeApp, SupportedPlatformsType } from './utils/open_app'; + +bothPlatformsIt({ + title: 'Send emoji react community', + risk: 'medium', + countOfDevicesNeeded: 2, + testCb: sendEmojiReactionCommunity, + allureSuites: { + parent: 'Sending Messages', + suite: 'Emoji reacts', + }, + allureDescription: 'Verifies that an emoji reaction can be sent and is received in a community', +}); + +async function sendEmojiReactionCommunity(platform: SupportedPlatformsType, testInfo: TestInfo) { + const message = `Testing emoji reacts - ${new Date().getTime()} - ${platform}`; + const { + devices: { alice1, bob1 }, + prebuilt: { alice }, + } = await test.step(TestSteps.SETUP.QA_SEEDER, async () => { + return open_Alice1_Bob1_friends({ + platform, + focusFriendsConvo: false, + testInfo, + }); + }); + await Promise.all( + [alice1, bob1].map(device => joinCommunity(device, testCommunityLink, testCommunityName)) + ); + await test.step(TestSteps.SEND.MESSAGE(alice.userName, testCommunityName), async () => { + await alice1.sendMessage(message); + }); + await test.step(TestSteps.SEND.EMOJI_REACT, async () => { + await bob1.scrollToBottom(); + await bob1.longPressMessage(message); + await bob1.clickOnElementAll(new FirstEmojiReact(bob1)); + // Verify long press menu disappeared (so next found emoji is in convo and not in react bar) + await bob1.verifyElementNotPresent({ + strategy: 'accessibility id', + selector: 'Reply to message', + }); + }); + await test.step(TestSteps.VERIFY.EMOJI_REACT, async () => { + await Promise.all( + [alice1, bob1].map(device => + device.waitForTextElementToBePresent(new EmojiReactsPill(device)) + ) + ); + }); + await test.step(TestSteps.SETUP.CLOSE_APP, async () => { + await closeApp(alice1, bob1); + }); +} diff --git a/run/test/specs/community_tests_image.spec.ts b/run/test/specs/community_tests_image.spec.ts index 7c2c4e510..6aca09d0a 100644 --- a/run/test/specs/community_tests_image.spec.ts +++ b/run/test/specs/community_tests_image.spec.ts @@ -2,40 +2,23 @@ import { test, type TestInfo } from '@playwright/test'; import { testCommunityLink, testCommunityName } from '../../constants/community'; import { TestSteps } from '../../types/allure'; -import { androidIt, iosIt } from '../../types/sessionIt'; -import { USERNAME } from '../../types/testing'; +import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; -import { newUser } from './utils/create_account'; import { joinCommunity } from './utils/join_community'; -import { closeApp, openAppOnPlatformSingleDevice, SupportedPlatformsType } from './utils/open_app'; +import { closeApp, SupportedPlatformsType } from './utils/open_app'; -// NOTE For some reason Appium takes FOREVER to load the iOS page source of the community on the recipients device -// and as such I haven't found a quick and easy way to verify that they see the new image message -// If this becomes a problem in the future then we can extract the unread count from page source and see it increment after the image gets sent -// But for now we have to trust that the sender seeing 'Sent' also delivers it to others on iOS -// This is also why it's a 1-device test and has its own iosIt definition (and not bothPlatformsItSeparate) - -androidIt({ +bothPlatformsIt({ title: 'Send image to community', risk: 'medium', countOfDevicesNeeded: 2, - testCb: sendImageCommunityAndroid, - allureSuites: { parent: 'Sending Messages', suite: 'Sending Attachments' }, + testCb: sendImageCommunity, + allureSuites: { parent: 'Sending Messages', suite: 'Attachments' }, allureDescription: 'Verifies that an image can be sent and received in a community', }); -iosIt({ - title: 'Send image to community', - risk: 'medium', - countOfDevicesNeeded: 1, - testCb: sendImageCommunityIOS, - allureSuites: { parent: 'Sending Messages', suite: 'Sending Attachments' }, - allureDescription: `Verifies that an image can be sent to a community. - Note that due to Appium's limitations, this test does not verify another device receiving the image.`, -}); - -async function sendImageCommunityAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { +async function sendImageCommunity(platform: SupportedPlatformsType, testInfo: TestInfo) { const { devices: { alice1, bob1 }, } = await test.step(TestSteps.SETUP.QA_SEEDER, async () => { @@ -48,45 +31,20 @@ async function sendImageCommunityAndroid(platform: SupportedPlatformsType, testI const testImageMessage = `Image message + ${new Date().getTime()} - ${platform}`; await test.step(TestSteps.NEW_CONVERSATION.JOIN_COMMUNITY, async () => { await Promise.all( - [alice1, bob1].map(async device => { - await joinCommunity(device, testCommunityLink, testCommunityName); - }) + [alice1, bob1].map(device => joinCommunity(device, testCommunityLink, testCommunityName)) ); }); await test.step(TestSteps.SEND.IMAGE, async () => { await alice1.sendImage(testImageMessage, true); }); await test.step(TestSteps.VERIFY.MESSAGE_RECEIVED, async () => { - await sleepFor(5000); // Give bob some time to receive the message so the test doesn't scroll down too early + await sleepFor(2000); // Give bob some time to receive the image await bob1.scrollToBottom(); - await bob1.trustAttachments(testCommunityName); - await bob1.scrollToBottom(); // Gotta keep scrolling down to make sure we're at the very bottom - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testImageMessage, - }); + await bob1.onAndroid().trustAttachments(testCommunityName); + await bob1.onAndroid().scrollToBottom(); // Trusting attachments scrolls the viewport up a bit so gotta scroll to bottom again + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, testImageMessage)); }); - await test.step(TestSteps.SETUP.CLOSE_APP, async () => { await closeApp(alice1, bob1); }); } -async function sendImageCommunityIOS(platform: SupportedPlatformsType, testInfo: TestInfo) { - const { device } = await test.step(TestSteps.SETUP.NEW_USER, async () => { - const { device } = await openAppOnPlatformSingleDevice(platform, testInfo); - await newUser(device, USERNAME.ALICE, { saveUserData: false }); - return { device }; - }); - const testImageMessage = `Image message + ${new Date().getTime()} - ${platform}`; - await test.step(TestSteps.NEW_CONVERSATION.JOIN_COMMUNITY, async () => { - await joinCommunity(device, testCommunityLink, testCommunityName); - }); - await test.step(TestSteps.SEND.IMAGE, async () => { - await device.sendImage(testImageMessage, true); - }); - - await test.step(TestSteps.SETUP.CLOSE_APP, async () => { - await closeApp(device); - }); -} diff --git a/run/test/specs/disappear_after_read.spec.ts b/run/test/specs/disappear_after_read.spec.ts index aeabf7bd2..ee22115d6 100644 --- a/run/test/specs/disappear_after_read.spec.ts +++ b/run/test/specs/disappear_after_read.spec.ts @@ -2,6 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DISAPPEARING_TIMES, DisappearModes } from '../../types/testing'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { checkDisappearingControlMessage } from './utils/disappearing_control_messages'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -53,22 +54,15 @@ async function disappearAfterRead(platform: SupportedPlatformsType, testInfo: Te ); // Send message to verify that deletion is working await alice1.sendMessage(testMessage); - await Promise.all([ - alice1.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - maxWait, - preventEarlyDeletion: true, - }), - bob1.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - maxWait, - preventEarlyDeletion: true, - }), - ]); + await Promise.all( + [alice1, bob1].map(device => + device.hasElementBeenDeleted({ + ...new MessageBody(device, testMessage).build(), + maxWait, + preventEarlyDeletion: true, + }) + ) + ); // Great success await closeApp(alice1, bob1); } diff --git a/run/test/specs/disappear_after_send.spec.ts b/run/test/specs/disappear_after_send.spec.ts index bebfb5226..709cec7de 100644 --- a/run/test/specs/disappear_after_send.spec.ts +++ b/run/test/specs/disappear_after_send.spec.ts @@ -2,6 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DisappearActions, DISAPPEARING_TIMES, DisappearModes } from '../../types/testing'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { checkDisappearingControlMessage } from './utils/disappearing_control_messages'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -53,21 +54,15 @@ async function disappearAfterSend(platform: SupportedPlatformsType, testInfo: Te // Send message to verify that deletion is working await alice1.sendMessage(testMessage); // Wait for message to disappear - await Promise.all([ - alice1.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - maxWait, - preventEarlyDeletion: true, - }), - bob1.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - maxWait, - }), - ]); + await Promise.all( + [alice1, bob1].map(device => + device.hasElementBeenDeleted({ + ...new MessageBody(device, testMessage).build(), + maxWait, + preventEarlyDeletion: true, + }) + ) + ); // Great success await closeApp(alice1, bob1); diff --git a/run/test/specs/disappear_after_send_groups.spec.ts b/run/test/specs/disappear_after_send_groups.spec.ts index 8c711e8f5..3c32a7d36 100644 --- a/run/test/specs/disappear_after_send_groups.spec.ts +++ b/run/test/specs/disappear_after_send_groups.spec.ts @@ -3,6 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DisappearActions, DISAPPEARING_TIMES } from '../../types/testing'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; import { setDisappearingMessage } from './utils/set_disappearing_messages'; @@ -56,10 +57,8 @@ async function disappearAfterSendGroups(platform: SupportedPlatformsType, testIn await Promise.all( [alice1, bob1, charlie1].map(device => device.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', + ...new MessageBody(device, testMessage).build(), maxWait, - text: testMessage, preventEarlyDeletion: true, }) ) diff --git a/run/test/specs/disappear_after_send_note_to_self.spec.ts b/run/test/specs/disappear_after_send_note_to_self.spec.ts index 896432dd9..dc38c8bea 100644 --- a/run/test/specs/disappear_after_send_note_to_self.spec.ts +++ b/run/test/specs/disappear_after_send_note_to_self.spec.ts @@ -2,6 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DisappearActions, DISAPPEARING_TIMES, USERNAME } from '../../types/testing'; +import { MessageBody } from './locators/conversation'; import { PlusButton } from './locators/home'; import { EnterAccountID, NewMessageOption, NextButton } from './locators/start_conversation'; import { sleepFor } from './utils'; @@ -49,9 +50,7 @@ async function disappearAfterSendNoteToSelf(platform: SupportedPlatformsType, te ); await device.sendMessage(testMessage); await device.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, + ...new MessageBody(device, testMessage).build(), maxWait, preventEarlyDeletion: true, }); diff --git a/run/test/specs/disappearing_image.spec.ts b/run/test/specs/disappearing_image.spec.ts index cce7de960..d40cf53a8 100644 --- a/run/test/specs/disappearing_image.spec.ts +++ b/run/test/specs/disappearing_image.spec.ts @@ -2,6 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DISAPPEARING_TIMES } from '../../types/testing'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -37,10 +38,8 @@ async function disappearingImageMessage1o1(platform: SupportedPlatformsType, tes await alice1.sendImage(testMessage); await Promise.all([ alice1.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', + ...new MessageBody(alice1, testMessage).build(), maxWait, - text: testMessage, preventEarlyDeletion: true, }), bob1.hasElementBeenDeleted({ diff --git a/run/test/specs/disappearing_link.spec.ts b/run/test/specs/disappearing_link.spec.ts index 22c229e38..4a74d3d73 100644 --- a/run/test/specs/disappearing_link.spec.ts +++ b/run/test/specs/disappearing_link.spec.ts @@ -6,7 +6,12 @@ import { TestSteps } from '../../types/allure'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; import { DISAPPEARING_TIMES } from '../../types/testing'; import { LinkPreview, LinkPreviewMessage } from './locators'; -import { MessageInput, OutgoingMessageStatusSent, SendButton } from './locators/conversation'; +import { + MessageBody, + MessageInput, + OutgoingMessageStatusSent, + SendButton, +} from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -72,10 +77,8 @@ async function disappearingLinkMessage1o1Ios(platform: SupportedPlatformsType, t await Promise.all( [alice1, bob1].map(device => device.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', + ...new MessageBody(device, testLink).build(), maxWait, - text: testLink, preventEarlyDeletion: true, }) ) diff --git a/run/test/specs/disappearing_video.spec.ts b/run/test/specs/disappearing_video.spec.ts index cf0736a72..9f939cd25 100644 --- a/run/test/specs/disappearing_video.spec.ts +++ b/run/test/specs/disappearing_video.spec.ts @@ -2,6 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DISAPPEARING_TIMES, USERNAME } from '../../types/testing'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; import { setDisappearingMessage } from './utils/set_disappearing_messages'; @@ -41,11 +42,9 @@ async function disappearingVideoMessage1o1(platform: SupportedPlatformsType, tes await Promise.all( [alice1, bob1].map(device => device.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', + ...new MessageBody(device, testMessage).build(), initialMaxWait, maxWait, - text: testMessage, preventEarlyDeletion: true, }) ) diff --git a/run/test/specs/disappearing_voice.spec.ts b/run/test/specs/disappearing_voice.spec.ts index 39ee0ffcf..f0e39163a 100644 --- a/run/test/specs/disappearing_voice.spec.ts +++ b/run/test/specs/disappearing_voice.spec.ts @@ -1,7 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; -import { DISAPPEARING_TIMES, USERNAME } from '../../types/testing'; +import { DISAPPEARING_TIMES } from '../../types/testing'; import { open_Alice1_Bob1_friends } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; import { setDisappearingMessage } from './utils/set_disappearing_messages'; @@ -36,7 +36,6 @@ async function disappearingVoiceMessage1o1(platform: SupportedPlatformsType, tes strategy: 'accessibility id', selector: 'Voice message', }); - await bob1.trustAttachments(USERNAME.ALICE); await Promise.all([ alice1.hasElementBeenDeleted({ strategy: 'accessibility id', @@ -46,7 +45,7 @@ async function disappearingVoiceMessage1o1(platform: SupportedPlatformsType, tes }), bob1.hasElementBeenDeleted({ strategy: 'accessibility id', - selector: 'Voice message', + selector: 'Untrusted attachment message', maxWait, preventEarlyDeletion: true, }), diff --git a/run/test/specs/group_disappearing_messages_image.spec.ts b/run/test/specs/group_disappearing_messages_image.spec.ts index 41144319f..a388a8dd6 100644 --- a/run/test/specs/group_disappearing_messages_image.spec.ts +++ b/run/test/specs/group_disappearing_messages_image.spec.ts @@ -2,6 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DISAPPEARING_TIMES } from '../../types/testing'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; import { setDisappearingMessage } from './utils/set_disappearing_messages'; @@ -44,10 +45,8 @@ async function disappearingImageMessageGroup(platform: SupportedPlatformsType, t await Promise.all( [alice1, bob1, charlie1].map(device => device.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', + ...new MessageBody(device, testMessage).build(), maxWait, - text: testMessage, preventEarlyDeletion: true, }) ) diff --git a/run/test/specs/group_disappearing_messages_link.spec.ts b/run/test/specs/group_disappearing_messages_link.spec.ts index d71f1fe04..fdd345e6e 100644 --- a/run/test/specs/group_disappearing_messages_link.spec.ts +++ b/run/test/specs/group_disappearing_messages_link.spec.ts @@ -6,7 +6,12 @@ import { TestSteps } from '../../types/allure'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DISAPPEARING_TIMES } from '../../types/testing'; import { LinkPreviewMessage } from './locators'; -import { MessageInput, OutgoingMessageStatusSent, SendButton } from './locators/conversation'; +import { + MessageBody, + MessageInput, + OutgoingMessageStatusSent, + SendButton, +} from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -71,10 +76,8 @@ async function disappearingLinkMessageGroup(platform: SupportedPlatformsType, te await Promise.all( [alice1, bob1, charlie1].map(device => device.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', + ...new MessageBody(device, testLink).build(), maxWait, - text: testLink, preventEarlyDeletion: true, }) ) diff --git a/run/test/specs/group_disappearing_messages_video.spec.ts b/run/test/specs/group_disappearing_messages_video.spec.ts index 31618ff30..8a7b698f7 100644 --- a/run/test/specs/group_disappearing_messages_video.spec.ts +++ b/run/test/specs/group_disappearing_messages_video.spec.ts @@ -2,6 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DISAPPEARING_TIMES } from '../../types/testing'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; import { setDisappearingMessage } from './utils/set_disappearing_messages'; @@ -45,10 +46,8 @@ async function disappearingVideoMessageGroup(platform: SupportedPlatformsType, t await Promise.all( [alice1, bob1, charlie1].map(device => device.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Message body', + ...new MessageBody(device, testMessage).build(), maxWait, - text: testMessage, preventEarlyDeletion: true, }) ) diff --git a/run/test/specs/group_message_delete.spec.ts b/run/test/specs/group_message_delete.spec.ts index c4fff3034..e59be0fd5 100644 --- a/run/test/specs/group_message_delete.spec.ts +++ b/run/test/specs/group_message_delete.spec.ts @@ -3,7 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DeleteMessageConfirmationModal, DeleteMessageLocally } from './locators'; -import { DeletedMessage } from './locators/conversation'; +import { DeletedMessage, MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -25,18 +25,11 @@ async function deleteMessageGroup(platform: SupportedPlatformsType, testInfo: Te testInfo, }); const sentMessage = await alice1.sendMessage('Checking local delete functionality'); - await Promise.all([ - bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }), - ]); + await Promise.all( + [bob1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, sentMessage)) + ) + ); // Select and long press on message to delete it await alice1.longPressMessage(sentMessage); // Select Delete icon @@ -52,17 +45,10 @@ async function deleteMessageGroup(platform: SupportedPlatformsType, testInfo: Te await alice1.waitForTextElementToBePresent(new DeletedMessage(alice1)); // Excellent // Check device 2 and 3 that message is still visible - await Promise.all([ - bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }), - ]); + await Promise.all( + [bob1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, sentMessage)) + ) + ); await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/group_message_document.spec.ts b/run/test/specs/group_message_document.spec.ts index 5f046e520..930302f78 100644 --- a/run/test/specs/group_message_document.spec.ts +++ b/run/test/specs/group_message_document.spec.ts @@ -1,6 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -28,30 +29,19 @@ async function sendDocumentGroupiOS(platform: SupportedPlatformsType, testInfo: focusGroupConvo: true, testInfo, }); - const testMessage = 'Testing-document-1'; + const testMessage = 'Testing-document'; const replyMessage = `Replying to document from ${alice.userName}`; await alice1.sendDocument(); - await Promise.all([ - bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }), - ]); + await Promise.all( + [bob1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, testMessage)) + ) + ); await bob1.longPressMessage(testMessage); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, replyMessage)); await closeApp(alice1, bob1, charlie1); } @@ -91,18 +81,11 @@ async function sendDocumentGroupAndroid(platform: SupportedPlatformsType, testIn await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); // Check reply from device 2 came through on alice1 and charlie1 - await Promise.all([ - alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }), - ]); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)) + ) + ); // Close app and server await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/group_message_gif.spec.ts b/run/test/specs/group_message_gif.spec.ts index dc9628311..fae04e543 100644 --- a/run/test/specs/group_message_gif.spec.ts +++ b/run/test/specs/group_message_gif.spec.ts @@ -1,6 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -46,16 +47,11 @@ async function sendGifGroupiOS(platform: SupportedPlatformsType, testInfo: TestI // Check reply came through on alice1 await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)) + ) + ); await closeApp(alice1, bob1, charlie1); } @@ -93,16 +89,11 @@ async function sendGifGroupAndroid(platform: SupportedPlatformsType, testInfo: T // Check reply came through on alice1 await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)) + ) + ); // Close app await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/group_message_image.spec.ts b/run/test/specs/group_message_image.spec.ts index 14cd14d76..92be52112 100644 --- a/run/test/specs/group_message_image.spec.ts +++ b/run/test/specs/group_message_image.spec.ts @@ -1,7 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; -import { OutgoingMessageStatusSent } from './locators/conversation'; +import { MessageBody, OutgoingMessageStatusSent } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -36,35 +36,23 @@ async function sendImageGroupiOS(platform: SupportedPlatformsType, testInfo: Tes ...new OutgoingMessageStatusSent(alice1).build(), maxWait: 50000, }); - await Promise.all([ - bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - maxWait: 5000, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - maxWait: 5000, - }), - ]); + await Promise.all( + [bob1, charlie1].map(device => + device.waitForTextElementToBePresent({ + ...new MessageBody(device, testMessage).build(), + maxWait: 5_000, + }) + ) + ); const replyMessage = await bob1.replyToMessage(alice, testMessage); - await Promise.all([ - alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - maxWait: 5000, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - maxWait: 5000, - }), - ]); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent({ + ...new MessageBody(device, replyMessage).build(), + maxWait: 5_000, + }) + ) + ); // Close server and devices await closeApp(alice1, bob1, charlie1); } @@ -106,18 +94,14 @@ async function sendImageGroupAndroid(platform: SupportedPlatformsType, testInfo: await bob1.longPress('Media message'); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await Promise.all([ - alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }), - ]); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent({ + ...new MessageBody(device, replyMessage).build(), + maxWait: 5_000, + }) + ) + ); // Close server and devices await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/group_message_link_preview.spec.ts b/run/test/specs/group_message_link_preview.spec.ts index 2d01006b4..3652a12c3 100644 --- a/run/test/specs/group_message_link_preview.spec.ts +++ b/run/test/specs/group_message_link_preview.spec.ts @@ -4,7 +4,12 @@ import { testLink } from '../../constants'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; import { LinkPreview, LinkPreviewMessage } from './locators'; -import { MessageInput, OutgoingMessageStatusSent, SendButton } from './locators/conversation'; +import { + MessageBody, + MessageInput, + OutgoingMessageStatusSent, + SendButton, +} from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -52,30 +57,20 @@ async function sendLinkGroupiOS(platform: SupportedPlatformsType, testInfo: Test await alice1.inputText(testLink, new MessageInput(alice1)); await alice1.waitForTextElementToBePresent(new LinkPreview(alice1)); await alice1.clickOnElementAll(new SendButton(alice1)); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testLink, - }); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testLink, - }); + await Promise.all( + [bob1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, testLink)) + ) + ); // Reply to link await bob1.longPressMessage(testLink); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)) + ) + ); await closeApp(alice1, bob1, charlie1); } @@ -115,17 +110,10 @@ async function sendLinkGroupAndroid(platform: SupportedPlatformsType, testInfo: await bob1.longPressMessage(testLink); await bob1.clickOnByAccessibilityID('Reply to message'); const replyMessage = await bob1.sendMessage(`${alice.userName} message reply`); - await Promise.all([ - alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }), - ]); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)) + ) + ); await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/group_message_long_text.spec.ts b/run/test/specs/group_message_long_text.spec.ts index d5c7ad9ae..a18943372 100644 --- a/run/test/specs/group_message_long_text.spec.ts +++ b/run/test/specs/group_message_long_text.spec.ts @@ -1,29 +1,24 @@ import type { TestInfo } from '@playwright/test'; import { longText } from '../../constants'; -import { bothPlatformsItSeparate } from '../../types/sessionIt'; -import { OutgoingMessageStatusSent } from './locators/conversation'; -import { ConversationItem } from './locators/home'; +import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; -bothPlatformsItSeparate({ +bothPlatformsIt({ title: 'Send long message to group', risk: 'low', countOfDevicesNeeded: 3, - ios: { - testCb: sendLongMessageGroupiOS, - }, - android: { - testCb: sendLongMessageGroupAndroid, - }, + testCb: sendLongMessageGroup, allureDescription: 'Verifies that a long message can be sent to a group', + allureLinks: { + android: 'SES-4337', + }, }); -async function sendLongMessageGroupiOS(platform: SupportedPlatformsType, testInfo: TestInfo) { +async function sendLongMessageGroup(platform: SupportedPlatformsType, testInfo: TestInfo) { const testGroupName = 'Message checks for groups'; - // Sending a long text message - const { devices: { alice1, bob1, charlie1 }, prebuilt: { alice }, @@ -34,82 +29,18 @@ async function sendLongMessageGroupiOS(platform: SupportedPlatformsType, testInf testInfo, }); await alice1.sendMessage(longText); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: longText, - }); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: longText, - }); + await Promise.all( + [bob1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, longText)) + ) + ); const replyMessage = await bob1.replyToMessage(alice, longText); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - // Close app - await closeApp(alice1, bob1, charlie1); -} - -async function sendLongMessageGroupAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { - const testGroupName = 'Message checks for groups'; - - const { - devices: { alice1, bob1, charlie1 }, - prebuilt: { alice }, - } = await open_Alice1_Bob1_Charlie1_friends_group({ - platform, - groupName: testGroupName, - focusGroupConvo: true, - testInfo, - }); - - // Sending a long text message - await alice1.sendMessage(longText); - await alice1.waitForTextElementToBePresent({ - ...new OutgoingMessageStatusSent(alice1).build(), - maxWait: 50000, - }); - - await Promise.all([ - bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: longText, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: longText, - }), - ]); - await bob1.longPressMessage(longText); - await bob1.clickOnByAccessibilityID('Reply to message'); - const replyMessage = await bob1.sendMessage(`${alice.userName} message reply`); - // Go out and back into the group to see the last message - await alice1.navigateBack(); - await alice1.clickOnElementAll(new ConversationItem(alice1, testGroupName)); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - // Go out and back into the group to see the last message - await charlie1.navigateBack(); - await charlie1.clickOnElementAll(new ConversationItem(charlie1, testGroupName)); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await Promise.all( + [alice1, charlie1].map(async device => { + await device.scrollToBottom(); + await device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)); + }) + ); // Close app await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/group_message_unsend.spec.ts b/run/test/specs/group_message_unsend.spec.ts index fb7420033..12384b49a 100644 --- a/run/test/specs/group_message_unsend.spec.ts +++ b/run/test/specs/group_message_unsend.spec.ts @@ -3,7 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DeleteMessageConfirmationModal, DeleteMessageForEveryone } from './locators'; -import { DeletedMessage } from './locators/conversation'; +import { DeletedMessage, MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -26,18 +26,11 @@ async function unsendMessageGroup(platform: SupportedPlatformsType, testInfo: Te testInfo, }); const sentMessage = await alice1.sendMessage('Checking unsend functionality'); - await Promise.all([ - bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }), - ]); + await Promise.all( + [bob1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, sentMessage)) + ) + ); // Select and long press on message to delete it await alice1.longPressMessage(sentMessage); // Select Delete icon diff --git a/run/test/specs/group_message_video.spec.ts b/run/test/specs/group_message_video.spec.ts index af098bd2f..8cc670414 100644 --- a/run/test/specs/group_message_video.spec.ts +++ b/run/test/specs/group_message_video.spec.ts @@ -1,6 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -31,35 +32,19 @@ async function sendVideoGroupiOS(platform: SupportedPlatformsType, testInfo: Tes const testMessage = 'Testing-video-1'; const replyMessage = `Replying to video from ${alice.userName} in ${testGroupName}`; await alice1.sendVideoiOS(testMessage); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - maxWait: 5000, - }); + await Promise.all( + [bob1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, testMessage)) + ) + ); await bob1.longPressMessage(testMessage); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - await charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)) + ) + ); // Close server and devices await closeApp(alice1, bob1, charlie1); } @@ -110,18 +95,11 @@ async function sendVideoGroupAndroid(platform: SupportedPlatformsType, testInfo: await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); // Check reply appears in device 1 and device 3 - await Promise.all([ - alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }), - charlie1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }), - ]); + await Promise.all( + [alice1, charlie1].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)) + ) + ); // Close app and server await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/group_message_voice.spec.ts b/run/test/specs/group_message_voice.spec.ts index 9c7d342be..f3ce8bcec 100644 --- a/run/test/specs/group_message_voice.spec.ts +++ b/run/test/specs/group_message_voice.spec.ts @@ -1,22 +1,18 @@ import type { TestInfo } from '@playwright/test'; -import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; -bothPlatformsItSeparate({ +bothPlatformsIt({ title: 'Send voice message to group', risk: 'high', countOfDevicesNeeded: 3, - ios: { - testCb: sendVoiceMessageGroupiOS, - }, - android: { - testCb: sendVoiceMessageGroupAndroid, - }, + testCb: sendVoiceMessageGroup, }); -async function sendVoiceMessageGroupiOS(platform: SupportedPlatformsType, testInfo: TestInfo) { +async function sendVoiceMessageGroup(platform: SupportedPlatformsType, testInfo: TestInfo) { const testGroupName = 'Message checks for groups'; const { devices: { alice1, bob1, charlie1 }, @@ -30,49 +26,8 @@ async function sendVoiceMessageGroupiOS(platform: SupportedPlatformsType, testIn const replyMessage = `Replying to voice message from ${alice.userName} in ${testGroupName}`; await alice1.sendVoiceMessage(); await Promise.all( - [alice1, bob1, charlie1].map(device => - device.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Voice message', - }) - ) - ); - await bob1.longPress('Voice message'); - await bob1.clickOnByAccessibilityID('Reply to message'); - await bob1.sendMessage(replyMessage); - await Promise.all( - [alice1, charlie1].map(device => - device.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }) - ) + [bob1, charlie1].map(device => device.onAndroid().trustAttachments(testGroupName)) ); - // Close server and devices - await closeApp(alice1, bob1, charlie1); -} - -async function sendVoiceMessageGroupAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { - // open devices - const testGroupName = 'Message checks for groups'; - const { - devices: { alice1, bob1, charlie1 }, - prebuilt: { alice }, - } = await open_Alice1_Bob1_Charlie1_friends_group({ - platform, - groupName: testGroupName, - focusGroupConvo: true, - testInfo, - }); - const replyMessage = `Replying to voice message from ${alice.userName} in ${testGroupName}`; - // Select voice message button to activate recording state - await alice1.sendVoiceMessage(); - await Promise.all([ - bob1.trustAttachments(testGroupName), - charlie1.trustAttachments(testGroupName), - ]); - // Check device 2 and 3 for voice message from user A await Promise.all( [alice1, bob1, charlie1].map(device => device.waitForTextElementToBePresent({ @@ -81,19 +36,14 @@ async function sendVoiceMessageGroupAndroid(platform: SupportedPlatformsType, te }) ) ); - // Reply to voice message await bob1.longPress('Voice message'); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - // Check device 1 and 3 for reply to appear await Promise.all( [alice1, charlie1].map(device => - device.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }) + device.waitForTextElementToBePresent(new MessageBody(device, replyMessage)) ) ); + // Close server and devices await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/group_reaction.spec.ts b/run/test/specs/group_reaction.spec.ts new file mode 100644 index 000000000..496a450f6 --- /dev/null +++ b/run/test/specs/group_reaction.spec.ts @@ -0,0 +1,64 @@ +import { test, type TestInfo } from '@playwright/test'; + +import { TestSteps } from '../../types/allure'; +import { bothPlatformsIt } from '../../types/sessionIt'; +import { GROUPNAME } from '../../types/testing'; +import { EmojiReactsCount, EmojiReactsPill, FirstEmojiReact } from './locators/conversation'; +import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; +import { closeApp, SupportedPlatformsType } from './utils/open_app'; + +bothPlatformsIt({ + title: 'Send emoji react groups', + risk: 'high', + countOfDevicesNeeded: 3, + testCb: sendEmojiReactionGroup, + allureSuites: { + parent: 'Sending Messages', + suite: 'Emoji reacts', + }, + allureDescription: 'Verifies that an emoji reaction can be sent and is received in a group.', +}); + +async function sendEmojiReactionGroup(platform: SupportedPlatformsType, testInfo: TestInfo) { + const groupName: GROUPNAME = 'Message checks for groups'; + const message = 'Testing emoji reacts'; + const { + devices: { alice1, bob1, charlie1 }, + prebuilt: { alice }, + } = await test.step(TestSteps.SETUP.QA_SEEDER, async () => { + return await open_Alice1_Bob1_Charlie1_friends_group({ + platform, + focusGroupConvo: true, + groupName: groupName, + testInfo, + }); + }); + await test.step(TestSteps.SEND.MESSAGE(alice.userName, groupName), async () => { + await alice1.sendMessage(message); + }); + await test.step(TestSteps.SEND.EMOJI_REACT, async () => { + await Promise.all( + [bob1, charlie1].map(async device => { + await device.longPressMessage(message); + await device.clickOnElementAll(new FirstEmojiReact(device)); + // Verify long press menu disappeared (so next found emoji is in convo and not in react bar) + await device.verifyElementNotPresent({ + strategy: 'accessibility id', + selector: 'Reply to message', + }); + }) + ); + }); + await test.step(TestSteps.VERIFY.EMOJI_REACT, async () => { + // All clients witness emoji and "2" count + await Promise.all( + [alice1, bob1, charlie1].map(async device => { + await device.waitForTextElementToBePresent(new EmojiReactsPill(device)); + await device.waitForTextElementToBePresent(new EmojiReactsCount(device)); + }) + ); + }); + await test.step(TestSteps.SETUP.CLOSE_APP, async () => { + await closeApp(alice1, bob1, charlie1); + }); +} diff --git a/run/test/specs/group_tests_add_contact.spec.ts b/run/test/specs/group_tests_add_contact.spec.ts index ddf444e8f..1981a6331 100644 --- a/run/test/specs/group_tests_add_contact.spec.ts +++ b/run/test/specs/group_tests_add_contact.spec.ts @@ -68,8 +68,7 @@ async function addContactToGroup(platform: SupportedPlatformsType, testInfo: Tes await unknown1.navigateBack(); // Leave Message Requests screen (Android) await unknown1.onAndroid().navigateBack(); - await unknown1.selectByText('Conversation list item', group.groupName); - // Check for control message on device 4 + await unknown1.clickOnElementAll(new ConversationItem(unknown1, group.groupName)); // Check for control message on device 4 await unknown1.waitForControlMessageToBePresent(englishStrippedStr('groupInviteYou').toString()); await closeApp(alice1, bob1, charlie1, unknown1); } diff --git a/run/test/specs/group_tests_mentions.spec.ts b/run/test/specs/group_tests_mentions.spec.ts index 7abf2e39f..e045f88a3 100644 --- a/run/test/specs/group_tests_mentions.spec.ts +++ b/run/test/specs/group_tests_mentions.spec.ts @@ -1,6 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_Charlie1_friends_group } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -25,11 +26,7 @@ async function mentionsForGroups(platform: SupportedPlatformsType, testInfo: Tes await alice1.mentionContact(platform, bob); // Check format on User B's device - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: `@You`, - }); + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, '@You')); // await device2.findMessageWithBody(`@You`); // Bob to Select User C await bob1.mentionContact(platform, charlie); diff --git a/run/test/specs/invite_a_friend_share.spec.ts b/run/test/specs/invite_a_friend_share.spec.ts index a40a0147d..88ae0d575 100644 --- a/run/test/specs/invite_a_friend_share.spec.ts +++ b/run/test/specs/invite_a_friend_share.spec.ts @@ -3,8 +3,9 @@ import type { TestInfo } from '@playwright/test'; import { IOS_XPATHS } from '../../constants'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; +import { AccountIDDisplay } from './locators/global'; import { PlusButton } from './locators/home'; -import { AccountIDField, InviteAFriendOption, ShareButton } from './locators/start_conversation'; +import { InviteAFriendOption, ShareButton } from './locators/start_conversation'; import { newUser } from './utils/create_account'; import { closeApp, openAppOnPlatformSingleDevice, SupportedPlatformsType } from './utils/open_app'; @@ -25,7 +26,7 @@ async function inviteAFriend(platform: SupportedPlatformsType, testInfo: TestInf // Select Invite a Friend await device.clickOnElementAll(new InviteAFriendOption(device)); // Check for presence of Account ID field - await device.waitForTextElementToBePresent(new AccountIDField(device)); + await device.waitForTextElementToBePresent(new AccountIDDisplay(device)); // Tap Share await device.clickOnElementAll(new ShareButton(device)); // defining the "Hey..." message element to retrieve the share message from diff --git a/run/test/specs/linked_device.spec.ts b/run/test/specs/linked_device.spec.ts index 5931d00b1..ac0cfbefc 100644 --- a/run/test/specs/linked_device.spec.ts +++ b/run/test/specs/linked_device.spec.ts @@ -3,7 +3,8 @@ import type { TestInfo } from '@playwright/test'; import { USERNAME } from '@session-foundation/qa-seeder'; import { bothPlatformsIt } from '../../types/sessionIt'; -import { UsernameSettings } from './locators'; +import { UsernameDisplay } from './locators'; +import { AccountIDDisplay } from './locators/global'; import { UserSettings } from './locators/settings'; import { linkedDevice } from './utils/link_device'; import { closeApp, openAppTwoDevices, SupportedPlatformsType } from './utils/open_app'; @@ -29,15 +30,7 @@ async function linkDevice(platform: SupportedPlatformsType, testInfo: TestInfo) // Verify username and session ID match await alice2.clickOnElementAll(new UserSettings(alice2)); // Check username - await alice2.waitForTextElementToBePresent({ - ...new UsernameSettings(alice2).build(), - text: alice.userName, - }); - await alice2.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Account ID', - text: alice.accountID, - }); - + await alice2.waitForTextElementToBePresent(new UsernameDisplay(alice2, alice.userName)); + await alice2.waitForTextElementToBePresent(new AccountIDDisplay(alice2, alice.accountID)); await closeApp(alice1, alice2); } diff --git a/run/test/specs/linked_device_block_user.spec.ts b/run/test/specs/linked_device_block_user.spec.ts index c7ed79837..d288886ab 100644 --- a/run/test/specs/linked_device_block_user.spec.ts +++ b/run/test/specs/linked_device_block_user.spec.ts @@ -4,6 +4,7 @@ import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { BlockedContactsSettings, BlockUser, BlockUserConfirmationModal } from './locators'; import { BlockedBanner, ConversationSettings } from './locators/conversation'; +import { Contact } from './locators/global'; import { ConversationItem } from './locators/home'; import { ConversationsMenuItem, UserSettings } from './locators/settings'; import { open_Alice2_Bob1_friends } from './state_builder'; @@ -73,16 +74,8 @@ async function blockUserInConversationOptions( alice2.clickOnElementAll(new BlockedContactsSettings(alice2)), ]); await Promise.all([ - alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Contact', - text: bob.userName, - }), - alice2.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Contact', - text: bob.userName, - }), + alice1.waitForTextElementToBePresent(new Contact(alice1, bob.userName)), + alice2.waitForTextElementToBePresent(new Contact(alice2, bob.userName)), ]); // Close app await closeApp(alice1, bob1, alice2); diff --git a/run/test/specs/linked_device_change_username.spec.ts b/run/test/specs/linked_device_change_username.spec.ts index eb8ee325b..49e9312e5 100644 --- a/run/test/specs/linked_device_change_username.spec.ts +++ b/run/test/specs/linked_device_change_username.spec.ts @@ -1,29 +1,22 @@ import type { TestInfo } from '@playwright/test'; -import { englishStrippedStr } from '../../localizer/englishStrippedStr'; -import { bothPlatformsItSeparate } from '../../types/sessionIt'; -import { TickButton, UsernameInput, UsernameSettings } from './locators'; +import { bothPlatformsIt } from '../../types/sessionIt'; +import { ClearInputButton, EditUsernameButton, UsernameDisplay, UsernameInput } from './locators'; import { SaveNameChangeButton, UserSettings } from './locators/settings'; import { open_Alice2 } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; -bothPlatformsItSeparate({ +bothPlatformsIt({ title: 'Change username linked device', risk: 'medium', countOfDevicesNeeded: 2, - ios: { - testCb: changeUsernameLinkediOS, - }, - android: { - testCb: changeUsernameLinkedAndroid, - }, + testCb: changeUsernameLinked, }); -async function changeUsernameLinkediOS(platform: SupportedPlatformsType, testInfo: TestInfo) { +async function changeUsernameLinked(platform: SupportedPlatformsType, testInfo: TestInfo) { const { devices: { alice1, alice2 }, - prebuilt: { alice }, } = await open_Alice2({ platform, testInfo }); const newUsername = 'Alice in chains'; @@ -33,87 +26,13 @@ async function changeUsernameLinkediOS(platform: SupportedPlatformsType, testInf alice2.clickOnElementAll(new UserSettings(alice2)), ]); // select username - await alice1.clickOnElementAll(new UsernameSettings(alice1)); - await alice1.checkModalStrings( - englishStrippedStr('displayNameSet').toString(), - englishStrippedStr('displayNameVisible').toString() - ); + await alice1.clickOnElementAll(new EditUsernameButton(alice1)); // type in new username await sleepFor(100); - await alice1.deleteText(new UsernameInput(alice1)); + await alice1.onIOS().deleteText(new UsernameInput(alice1)); + await alice1.onAndroid().clickOnElementAll(new ClearInputButton(alice1)); await alice1.inputText(newUsername, new UsernameInput(alice1)); await alice1.clickOnElementAll(new SaveNameChangeButton(alice1)); - const username = await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Username', - text: newUsername, - }); - const changedUsername = await alice1.getTextFromElement(username); - if (changedUsername === alice.userName) { - throw new Error('Username change unsuccessful'); - } - await alice1.closeScreen(); - await alice1.clickOnElementAll(new UserSettings(alice1)); - await Promise.all([ - alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Username', - text: newUsername, - }), - alice2.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Username', - text: newUsername, - }), - ]); - await closeApp(alice1, alice2); -} - -async function changeUsernameLinkedAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { - const { - devices: { alice1, alice2 }, - prebuilt: { alice }, - } = await open_Alice2({ platform, testInfo }); - - const newUsername = 'Alice in chains'; - // click on settings/profile avatar - await Promise.all([ - alice1.clickOnElementAll(new UserSettings(alice1)), - alice2.clickOnElementAll(new UserSettings(alice2)), - ]); - // select username - await alice1.clickOnElementAll(new UsernameSettings(alice1)); - // type in new username - await sleepFor(100); - await alice1.deleteText(new UsernameInput(alice1)); - await alice1.inputText(newUsername, new UsernameInput(alice1)); - await alice1.clickOnElementAll(new TickButton(alice1)); - const usernameEl = await alice1.waitForTextElementToBePresent(new UsernameSettings(alice1)); - const changedUsername = await alice1.getTextFromElement(usernameEl); - if (changedUsername === alice.userName) { - throw new Error('Username change unsuccessful'); - } - // Get the initial linked username from alice2 - const username2 = await alice2.waitForTextElementToBePresent(new UsernameSettings(alice2)); - let currentLinkedUsername = await alice2.getTextFromElement(username2); - - let currentWait = 0; - const waitPerLoop = 500; - const maxWait = 50000; - - do { - await sleepFor(waitPerLoop); - // Close the screen and navigate back to the User Settings - await alice2.closeScreen(); - await alice2.clickOnElementAll(new UserSettings(alice2)); - currentWait += waitPerLoop; - const linkedUsernameEl = await alice2.waitForTextElementToBePresent( - new UsernameSettings(alice2) - ); - currentLinkedUsername = await alice2.getTextFromElement(linkedUsernameEl); - } while (currentLinkedUsername === alice.userName && currentWait < maxWait); - { - alice2.log('Username not changed yet'); - } + await alice2.waitForTextElementToBePresent(new UsernameDisplay(alice2, newUsername)); await closeApp(alice1, alice2); } diff --git a/run/test/specs/linked_device_create_group.spec.ts b/run/test/specs/linked_device_create_group.spec.ts index 88eb0ac8f..40899c26d 100644 --- a/run/test/specs/linked_device_create_group.spec.ts +++ b/run/test/specs/linked_device_create_group.spec.ts @@ -70,9 +70,7 @@ async function linkedGroupiOS(platform: SupportedPlatformsType, testInfo: TestIn // Wait 5 seconds for name to update await sleepFor(5000); // Check linked device for name change (conversation header name) - await device2.waitForTextElementToBePresent( - new ConversationHeaderName(device2).build(newGroupName) - ); + await device2.waitForTextElementToBePresent(new ConversationHeaderName(device2, newGroupName)); await Promise.all([ device2.waitForControlMessageToBePresent(groupNameNew), device3.waitForControlMessageToBePresent(groupNameNew), @@ -118,9 +116,7 @@ async function linkedGroupAndroid(platform: SupportedPlatformsType, testInfo: Te // Config message is "Group name is now {group_name}" await device1.waitForControlMessageToBePresent(groupNameNew); // Check linked device for name change (conversation header name) - await device2.waitForTextElementToBePresent( - new ConversationHeaderName(device2).build(newGroupName) - ); + await device2.waitForTextElementToBePresent(new ConversationHeaderName(device2, newGroupName)); await Promise.all([ device2.waitForControlMessageToBePresent(groupNameNew), device3.waitForControlMessageToBePresent(groupNameNew), diff --git a/run/test/specs/linked_device_delete_message.spec.ts b/run/test/specs/linked_device_delete_message.spec.ts index bba534748..97f35cf82 100644 --- a/run/test/specs/linked_device_delete_message.spec.ts +++ b/run/test/specs/linked_device_delete_message.spec.ts @@ -3,7 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DeleteMessageConfirmationModal } from './locators'; -import { DeletedMessage } from './locators/conversation'; +import { DeletedMessage, MessageBody } from './locators/conversation'; import { ConversationItem } from './locators/home'; import { open_Alice2_Bob1_friends } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -25,9 +25,7 @@ async function deletedMessageLinkedDevice(platform: SupportedPlatformsType, test const sentMessage = await alice1.sendMessage(testMessage); // Check message came through on linked device(3) // Enter conversation with user B on device 3 - await alice2.waitForTextElementToBePresent(new ConversationItem(alice2, bob.userName)); - await alice2.selectByText('Conversation list item', bob.userName); - // Find message + await alice2.clickOnElementAll(new ConversationItem(alice2, bob.userName)); // Find message await alice2.findMessageWithBody(sentMessage); // Select message on device 1, long press await alice1.longPressMessage(sentMessage); @@ -41,18 +39,11 @@ async function deletedMessageLinkedDevice(platform: SupportedPlatformsType, test // Check linked device for deleted message await alice1.waitForTextElementToBePresent(new DeletedMessage(alice1)); // Check device 2 and 3 for no change - await Promise.all([ - bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }), - alice2.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }), - ]); + await Promise.all( + [bob1, alice2].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, sentMessage)) + ) + ); // Close app await closeApp(alice1, bob1, alice2); } diff --git a/run/test/specs/linked_device_hide_note_to_self.spec.ts b/run/test/specs/linked_device_hide_note_to_self.spec.ts index 914a74cfa..1ca559ba1 100644 --- a/run/test/specs/linked_device_hide_note_to_self.spec.ts +++ b/run/test/specs/linked_device_hide_note_to_self.spec.ts @@ -46,7 +46,7 @@ async function hideNoteToSelf(platform: SupportedPlatformsType, testInfo: TestIn await test.step(TestSteps.VERIFY.GENERIC_MODAL, async () => { await alice1.checkModalStrings( englishStrippedStr('noteToSelfHide').toString(), - englishStrippedStr('hideNoteToSelfDescription').toString(), // This one fails on iOS, see SES-4144 + englishStrippedStr('hideNoteToSelfDescription').toString(), false ); }); diff --git a/run/test/specs/linked_device_profile_picture_syncs.spec.ts b/run/test/specs/linked_device_profile_picture_syncs.spec.ts index f8f7b83fb..abd561abb 100644 --- a/run/test/specs/linked_device_profile_picture_syncs.spec.ts +++ b/run/test/specs/linked_device_profile_picture_syncs.spec.ts @@ -26,10 +26,10 @@ async function avatarRestored(platform: SupportedPlatformsType, testInfo: TestIn } = await open_Alice2({ platform, testInfo }); await alice1.uploadProfilePicture(); await test.step(TestSteps.VERIFY.PROFILE_PICTURE_CHANGED, async () => { - await alice2.waitForElementColorMatch(new UserSettings(alice2), expectedPixelHexColor, { - maxWait: 20_000, - elementTimeout: 500, - }); + await alice2.waitForElementColorMatch( + { ...new UserSettings(alice2).build(), maxWait: 20_000 }, + expectedPixelHexColor + ); }); await closeApp(alice1, alice2); } diff --git a/run/test/specs/linked_device_restore_group.spec.ts b/run/test/specs/linked_device_restore_group.spec.ts index 59028e1de..2c77a9faf 100644 --- a/run/test/specs/linked_device_restore_group.spec.ts +++ b/run/test/specs/linked_device_restore_group.spec.ts @@ -2,7 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; -import { ConversationHeaderName } from './locators/conversation'; +import { ConversationHeaderName, MessageBody } from './locators/conversation'; import { ConversationItem } from './locators/home'; import { newUser } from './utils/create_account'; import { createGroup } from './utils/create_group'; @@ -32,36 +32,18 @@ async function restoreGroup(platform: SupportedPlatformsType, testInfo: TestInfo // Check that group has loaded on linked device await device4.clickOnElementAll(new ConversationItem(device4, testGroupName)); // Check the group name has loaded - await device4.waitForTextElementToBePresent( - new ConversationHeaderName(device4).build(testGroupName) - ); + await device4.waitForTextElementToBePresent(new ConversationHeaderName(device4, testGroupName)); // Check all messages are present await Promise.all([ - device4.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: aliceMessage, - }), - device4.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: bobMessage, - }), - device4.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: charlieMessage, - }), + device4.waitForTextElementToBePresent(new MessageBody(device4, aliceMessage)), + device4.waitForTextElementToBePresent(new MessageBody(device4, bobMessage)), + device4.waitForTextElementToBePresent(new MessageBody(device4, charlieMessage)), ]); const testMessage2 = 'Checking that message input is working'; await device4.sendMessage(testMessage2); await Promise.all( [device1, device2, device3].map(device => - device.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage2, - }) + device.waitForTextElementToBePresent(new MessageBody(device, testMessage2)) ) ); await closeApp(device1, device2, device3, device4); diff --git a/run/test/specs/locators/conversation.ts b/run/test/specs/locators/conversation.ts index f24e1a9b3..763b9a6e4 100644 --- a/run/test/specs/locators/conversation.ts +++ b/run/test/specs/locators/conversation.ts @@ -23,6 +23,25 @@ export class SendButton extends LocatorsInterface { } } +export class MessageBody extends LocatorsInterface { + public text: string | undefined; + constructor(device: DeviceWrapper, text?: string) { + super(device); + this.text = text; + } + public build() { + switch (this.platform) { + case 'android': + case 'ios': + return { + strategy: 'accessibility id', + selector: 'Message body', + text: this.text, + } as const; + } + } +} + export class ScrollToBottomButton extends LocatorsInterface { public build() { switch (this.platform) { @@ -130,19 +149,24 @@ export class CallButton extends LocatorsInterface { } export class ConversationHeaderName extends LocatorsInterface { - public build(text?: string) { + public text: string | undefined; + constructor(device: DeviceWrapper, text?: string) { + super(device); + this.text = text; + } + public build() { switch (this.platform) { case 'android': return { - strategy: 'id', - selector: 'Conversation header name', - text, + strategy: '-android uiautomator', + selector: `new UiSelector().resourceId("Conversation header name").childSelector(new UiSelector().resourceId("pro-badge-text"))`, + text: this.text, } as const; case 'ios': return { strategy: 'accessibility id', selector: 'Conversation header name', - text, + text: this.text, } as const; } } @@ -363,8 +387,8 @@ export class EditNicknameButton extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'id', - selector: 'edit-profile-icon', + strategy: 'accessibility id', + selector: 'Edit', } as const; case 'ios': return { @@ -432,3 +456,55 @@ export class PreferredDisplayName extends LocatorsInterface { } } } + +export class FirstEmojiReact extends LocatorsInterface { + public build() { + switch (this.platform) { + case 'android': + return { + strategy: 'id', + selector: 'network.loki.messenger.qa:id/reaction_1', + } as const; + case 'ios': + return { + strategy: 'accessibility id', + selector: '😂', + } as const; + } + } +} + +export class EmojiReactsPill extends LocatorsInterface { + public build() { + switch (this.platform) { + case 'android': + return { + strategy: 'id', + selector: 'network.loki.messenger.qa:id/layout_emoji_container', + } as const; + case 'ios': + return { + strategy: 'accessibility id', + selector: '😂', + } as const; + } + } +} + +export class EmojiReactsCount extends LocatorsInterface { + public build() { + switch (this.platform) { + case 'android': + return { + strategy: 'id', + selector: 'network.loki.messenger.qa:id/reactions_pill_count', + text: '2', + } as const; + case 'ios': + return { + strategy: 'accessibility id', + selector: '2', + } as const; + } + } +} diff --git a/run/test/specs/locators/global.ts b/run/test/specs/locators/global.ts index d45a5e650..c5c658d95 100644 --- a/run/test/specs/locators/global.ts +++ b/run/test/specs/locators/global.ts @@ -1,3 +1,4 @@ +import { DeviceWrapper } from '../../../types/DeviceWrapper'; import { LocatorsInterface } from './index'; export class ModalHeading extends LocatorsInterface { @@ -61,17 +62,24 @@ export class EnableLinkPreviewsModalButton extends LocatorsInterface { } export class Contact extends LocatorsInterface { + public text: string | undefined; + constructor(device: DeviceWrapper, text?: string) { + super(device); + this.text = text; + } public build() { switch (this.platform) { case 'android': return { strategy: 'id', - selector: 'Contact', + selector: 'pro-badge-text', + text: this.text, } as const; case 'ios': return { strategy: 'accessibility id', selector: 'Contact', + text: this.text, } as const; } } @@ -110,3 +118,27 @@ export class DenyPermissionLocator extends LocatorsInterface { } } } + +export class AccountIDDisplay extends LocatorsInterface { + public text: string | undefined; + constructor(device: DeviceWrapper, text?: string) { + super(device); + this.text = text; + } + public build() { + switch (this.platform) { + case 'android': + return { + strategy: 'id', + selector: 'Account ID', + text: this.text, + } as const; + case 'ios': + return { + strategy: 'accessibility id', + selector: 'Account ID', + text: this.text, + } as const; + } + } +} diff --git a/run/test/specs/locators/global_search.ts b/run/test/specs/locators/global_search.ts index b08aab9f4..3230131fc 100644 --- a/run/test/specs/locators/global_search.ts +++ b/run/test/specs/locators/global_search.ts @@ -7,7 +7,7 @@ export class NoteToSelfOption extends LocatorsInterface { case 'android': return { strategy: 'id', - selector: 'network.loki.messenger.qa:id/search_result_title', + selector: 'pro-badge-text', text: 'Note to Self', }; case 'ios': diff --git a/run/test/specs/locators/groups.ts b/run/test/specs/locators/groups.ts index 47e76a8c9..f994e7e9f 100644 --- a/run/test/specs/locators/groups.ts +++ b/run/test/specs/locators/groups.ts @@ -70,8 +70,8 @@ export class UpdateGroupInformation extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'id', - selector: 'group-name', + strategy: 'accessibility id', + selector: 'Edit', }; case 'ios': { const groupName = this.groupName; @@ -259,7 +259,7 @@ export class GroupMember extends LocatorsInterface { case 'android': return { strategy: 'id', - selector: 'Contact', + selector: 'pro-badge-text', text: `${username}`, } as const; case 'ios': diff --git a/run/test/specs/locators/home.ts b/run/test/specs/locators/home.ts index a9e11fad0..9829b0b2e 100644 --- a/run/test/specs/locators/home.ts +++ b/run/test/specs/locators/home.ts @@ -40,11 +40,15 @@ export class ConversationItem extends LocatorsInterface { this.text = text; } public build() { - return { - strategy: 'accessibility id', - selector: 'Conversation list item', - text: this.text, - } as const; + switch (this.platform) { + case 'android': + case 'ios': + return { + strategy: 'accessibility id', + selector: 'Conversation list item', + text: this.text, + } as const; + } } } diff --git a/run/test/specs/locators/index.ts b/run/test/specs/locators/index.ts index 860a106e5..af4b5888a 100644 --- a/run/test/specs/locators/index.ts +++ b/run/test/specs/locators/index.ts @@ -31,7 +31,16 @@ export abstract class LocatorsInterface { export function describeLocator(locator: StrategyExtractionObj & { text?: string }): string { const { strategy, selector, text } = locator; - const base = `${strategy} "${selector}"`; + + // Trim selector if its too long, show beginning and end + const maxSelectorLength = 80; + const halfLength = Math.floor(maxSelectorLength / 2); + const trimmedSelector = + selector.length > maxSelectorLength + ? `${selector.substring(0, halfLength)}…${selector.substring(selector.length - halfLength)}` + : selector; + + const base = `${strategy} "${trimmedSelector}"`; return text ? `${base} and text "${text}"` : base; } @@ -39,17 +48,6 @@ export function describeLocator(locator: StrategyExtractionObj & { text?: string export abstract class LocatorsInterfaceScreenshot extends LocatorsInterface { abstract screenshotFileName(state?: ElementStates): string; } -// When applying a nickname or username change -export class TickButton extends LocatorsInterface { - public build() { - switch (this.platform) { - case 'android': - return { strategy: 'accessibility id', selector: 'Set' } as const; - case 'ios': - return { strategy: 'accessibility id', selector: 'Done' } as const; - } - } -} export class ApplyChanges extends LocatorsInterface { public build() { @@ -86,13 +84,13 @@ export class ReadReceiptsButton extends LocatorsInterface { } } -export class ExitUserProfile extends LocatorsInterface { +export class CloseSettings extends LocatorsInterface { public build() { switch (this.platform) { case 'android': return { strategy: 'accessibility id', - selector: 'Navigate up', + selector: 'Close', } as const; case 'ios': return { @@ -103,13 +101,37 @@ export class ExitUserProfile extends LocatorsInterface { } } -export class UsernameSettings extends LocatorsInterface { +export class UsernameDisplay extends LocatorsInterface { + public text: string | undefined; + constructor(device: DeviceWrapper, text?: string) { + super(device); + this.text = text; + } + public build() { + switch (this.platform) { + case 'android': + return { + strategy: 'id', + selector: 'pro-badge-text', + text: this.text, + } as const; + case 'ios': + return { + strategy: 'accessibility id', + selector: 'Username', + text: this.text, + } as const; + } + } +} + +export class EditUsernameButton extends LocatorsInterface { public build() { switch (this.platform) { case 'android': return { strategy: 'accessibility id', - selector: 'Display name', + selector: 'Edit', } as const; case 'ios': return { @@ -125,8 +147,8 @@ export class UsernameInput extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'accessibility id', - selector: 'Enter display name', + strategy: 'class name', + selector: 'android.widget.EditText', } as const; case 'ios': return { @@ -137,6 +159,23 @@ export class UsernameInput extends LocatorsInterface { } } +export class ClearInputButton extends LocatorsInterface { + public build() { + switch (this.platform) { + case 'android': + return { + strategy: 'id', + selector: 'clear-input-button', + } as const; + case 'ios': + return { + strategy: 'id', + selector: 'clear-input-button', + } as const; + } + } +} + export class FirstGif extends LocatorsInterface { public build(): StrategyExtractionObj { switch (this.platform) { @@ -223,7 +262,7 @@ export class JoinCommunityButton extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'accessibility id', + strategy: 'id', selector: 'Join community button', }; case 'ios': @@ -240,7 +279,7 @@ export class CommunityInput extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'accessibility id', + strategy: 'id', selector: 'Community input', }; case 'ios': @@ -359,8 +398,9 @@ export class BlockedContactsSettings extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'accessibility id', - selector: 'Blocked contacts', + // Temporary fix until there's a unique ID + strategy: '-android uiautomator', + selector: `new UiSelector().text("View and manage blocked contacts.")`, }; case 'ios': return { diff --git a/run/test/specs/locators/settings.ts b/run/test/specs/locators/settings.ts index e9d741856..05c3628d5 100644 --- a/run/test/specs/locators/settings.ts +++ b/run/test/specs/locators/settings.ts @@ -44,6 +44,23 @@ export class UserSettings extends LocatorsInterface { } } +export class UserAvatar extends LocatorsInterface { + public build() { + switch (this.platform) { + case 'android': + return { + strategy: 'id', + selector: 'User settings', + } as const; + case 'ios': + return { + strategy: 'accessibility id', + selector: 'User settings', + } as const; + } + } +} + export class RecoveryPasswordMenuItem extends LocatorsInterface { public build() { switch (this.platform) { @@ -117,7 +134,7 @@ export class SaveNameChangeButton extends LocatorsInterface { case 'android': return { strategy: 'id', - selector: 'Save', + selector: 'update-username-confirm-button', } as const; case 'ios': return { @@ -165,8 +182,9 @@ export class ConversationsMenuItem extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'id', - selector: 'Conversations', + strategy: '-android uiautomator', + selector: + 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().resourceId("Conversations"))', } as const; case 'ios': return { @@ -182,8 +200,9 @@ export class AppearanceMenuItem extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'id', - selector: 'Appearance', + strategy: '-android uiautomator', + selector: + 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().resourceId("Appearance"))', } as const; case 'ios': return { @@ -199,8 +218,9 @@ export class SelectAppIcon extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'id', - selector: 'network.loki.messenger.qa:id/system_settings_app_icon', + strategy: '-android uiautomator', + selector: + 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().text("Select app icon"))', } as const; case 'ios': return { @@ -235,8 +255,10 @@ export class AppDisguiseMeetingIcon extends LocatorsInterface { selector: 'MeetingSE option', } as const; case 'ios': - // NOTE see SES-3809 - throw new Error('No locators implemented for iOS'); + return { + strategy: 'accessibility id', + selector: 'Meetings option', + } as const; } } } @@ -277,8 +299,9 @@ export class PathMenuItem extends LocatorsInterface { switch (this.platform) { case 'android': return { - strategy: 'xpath', - selector: `//android.widget.TextView[@text="Path"]`, + strategy: '-android uiautomator', + selector: + 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().resourceId("path-menu-item"))', } as const; case 'ios': return { diff --git a/run/test/specs/locators/start_conversation.ts b/run/test/specs/locators/start_conversation.ts index 2c7b911cd..92042db94 100644 --- a/run/test/specs/locators/start_conversation.ts +++ b/run/test/specs/locators/start_conversation.ts @@ -138,22 +138,6 @@ export class EnterAccountID extends LocatorsInterface { } // INVITE A FRIEND SECTION -export class AccountIDField extends LocatorsInterface { - public build() { - switch (this.platform) { - case 'android': - return { - strategy: 'id', - selector: 'Account ID', - } as const; - case 'ios': - return { - strategy: 'accessibility id', - selector: 'Account ID', - } as const; - } - } -} export class ShareButton extends LocatorsInterface { public build() { diff --git a/run/test/specs/message_deletion.spec.ts b/run/test/specs/message_deletion.spec.ts index f25df4c07..76c3059f3 100644 --- a/run/test/specs/message_deletion.spec.ts +++ b/run/test/specs/message_deletion.spec.ts @@ -3,7 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DeleteMessageConfirmationModal, DeleteMessageLocally } from './locators'; -import { DeletedMessage } from './locators/conversation'; +import { DeletedMessage, MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -23,11 +23,7 @@ async function deleteMessage(platform: SupportedPlatformsType, testInfo: TestInf }); // send message from User A to User B const sentMessage = await alice1.sendMessage('Checking local deletetion functionality'); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }); + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, sentMessage)); // Select and long press on message to delete it await alice1.longPressMessage(sentMessage); // Select Delete icon @@ -44,11 +40,7 @@ async function deleteMessage(platform: SupportedPlatformsType, testInfo: TestInf await alice1.waitForTextElementToBePresent(new DeletedMessage(alice1)); // Device 2 should show no change - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }); + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, sentMessage)); // Excellent await closeApp(alice1, bob1); diff --git a/run/test/specs/message_document.spec.ts b/run/test/specs/message_document.spec.ts index 60c7b0934..fc7422e7c 100644 --- a/run/test/specs/message_document.spec.ts +++ b/run/test/specs/message_document.spec.ts @@ -1,6 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -19,17 +20,13 @@ async function sendDocument(platform: SupportedPlatformsType, testInfo: TestInfo focusFriendsConvo: true, testInfo, }); - const testMessage = 'Testing-document-1'; + const testMessage = 'Testing documents'; const replyMessage = `Replying to document from ${alice.userName}`; await alice1.sendDocument(); await bob1.trustAttachments(alice.userName); // Reply to message - await bob1.onIOS().waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }); + await bob1.onIOS().waitForTextElementToBePresent(new MessageBody(bob1, testMessage)); await bob1.onAndroid().waitForTextElementToBePresent({ strategy: 'accessibility id', selector: 'Document', @@ -38,11 +35,7 @@ async function sendDocument(platform: SupportedPlatformsType, testInfo: TestInfo await bob1.onAndroid().longPress('Document'); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, replyMessage)); // Close app and server await closeApp(alice1, bob1); } diff --git a/run/test/specs/message_gif.spec.ts b/run/test/specs/message_gif.spec.ts index 1e7e08aed..1c8c8e887 100644 --- a/run/test/specs/message_gif.spec.ts +++ b/run/test/specs/message_gif.spec.ts @@ -1,23 +1,18 @@ import type { TestInfo } from '@playwright/test'; -import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; -import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; -bothPlatformsItSeparate({ +bothPlatformsIt({ title: 'Send GIF 1:1', risk: 'medium', countOfDevicesNeeded: 2, - ios: { - testCb: sendGifIos, - }, - android: { - testCb: sendGifAndroid, - }, + testCb: sendGif, }); -async function sendGifIos(platform: SupportedPlatformsType, testInfo: TestInfo) { +async function sendGif(platform: SupportedPlatformsType, testInfo: TestInfo) { const { devices: { alice1, bob1 }, prebuilt: { alice }, @@ -37,44 +32,7 @@ async function sendGifIos(platform: SupportedPlatformsType, testInfo: TestInfo) await bob1.longPress('Media message'); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - // Close app - await closeApp(alice1, bob1); -} - -async function sendGifAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { - // Test sending a video - // open devices and server - const { - devices: { alice1, bob1 }, - prebuilt: { alice }, - } = await open_Alice1_Bob1_friends({ - platform, - focusFriendsConvo: true, - testInfo, - }); - const replyMessage = `Replying to GIF from ${alice.userName}`; - // Click on attachments button - await alice1.sendGIF(); - // Check if the 'Tap to download media' config appears - // Click on config - await bob1.trustAttachments(alice.userName); - // Reply to message - await sleepFor(5000); - await bob1.longPress('Media message'); - // Check reply came through on alice1 - await bob1.clickOnByAccessibilityID('Reply to message'); - await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, replyMessage)); // Close app await closeApp(alice1, bob1); } diff --git a/run/test/specs/message_image.spec.ts b/run/test/specs/message_image.spec.ts index 22d5ceaa1..17eed7e09 100644 --- a/run/test/specs/message_image.spec.ts +++ b/run/test/specs/message_image.spec.ts @@ -1,6 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -26,19 +27,10 @@ async function sendImage(platform: SupportedPlatformsType, testInfo: TestInfo) { await alice1.sendImage(testMessage); // Trust message on device 2 (bob) await bob1.trustAttachments(alice.userName); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }); + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, testMessage)); // Reply to message (on device 2 - Bob) const replyMessage = await bob1.replyToMessage(bob, testMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, replyMessage)); // Close app and server await closeApp(alice1, bob1); } diff --git a/run/test/specs/message_link_preview.spec.ts b/run/test/specs/message_link_preview.spec.ts index 075b47679..d80076f55 100644 --- a/run/test/specs/message_link_preview.spec.ts +++ b/run/test/specs/message_link_preview.spec.ts @@ -4,7 +4,12 @@ import { testLink } from '../../constants'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; import { LinkPreview, LinkPreviewMessage } from './locators'; -import { MessageInput, OutgoingMessageStatusSent, SendButton } from './locators/conversation'; +import { + MessageBody, + MessageInput, + OutgoingMessageStatusSent, + SendButton, +} from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -51,20 +56,11 @@ async function sendLinkIos(platform: SupportedPlatformsType, testInfo: TestInfo) await alice1.waitForTextElementToBePresent(new LinkPreview(alice1)); await alice1.clickOnElementAll(new SendButton(alice1)); // Make sure image preview is available in device 2 - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testLink, - }); - + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, testLink)); await bob1.longPressMessage(testLink); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, replyMessage)); await closeApp(alice1, bob1); } diff --git a/run/test/specs/message_long_text.spec.ts b/run/test/specs/message_long_text.spec.ts index a86772ba6..1bde31726 100644 --- a/run/test/specs/message_long_text.spec.ts +++ b/run/test/specs/message_long_text.spec.ts @@ -2,6 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { longText } from '../../constants'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { ConversationItem } from './locators/home'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; @@ -17,6 +18,9 @@ bothPlatformsItSeparate({ android: { testCb: sendLongMessageAndroid, }, + allureLinks: { + android: 'SES-4337', + }, }); async function sendLongMessageIos(platform: SupportedPlatformsType, testInfo: TestInfo) { @@ -59,11 +63,7 @@ async function sendLongMessageAndroid(platform: SupportedPlatformsType, testInfo // Reply to message (User B to User A) const sentMessage = await bob1.replyToMessage(alice, longText); // Check reply came through on alice1 - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }); + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, sentMessage)); // Close app await closeApp(alice1, bob1); } diff --git a/run/test/specs/message_reaction.spec.ts b/run/test/specs/message_reaction.spec.ts new file mode 100644 index 000000000..aa7b4b78a --- /dev/null +++ b/run/test/specs/message_reaction.spec.ts @@ -0,0 +1,56 @@ +import { test, type TestInfo } from '@playwright/test'; + +import { TestSteps } from '../../types/allure'; +import { bothPlatformsIt } from '../../types/sessionIt'; +import { EmojiReactsPill, FirstEmojiReact } from './locators/conversation'; +import { open_Alice1_Bob1_friends } from './state_builder'; +import { closeApp, SupportedPlatformsType } from './utils/open_app'; + +bothPlatformsIt({ + title: 'Send emoji react 1:1', + risk: 'high', + countOfDevicesNeeded: 2, + testCb: sendEmojiReaction, + allureSuites: { + parent: 'Sending Messages', + suite: 'Emoji reacts', + }, + allureDescription: + 'Verifies that an emoji reaction can be sent and is received in a 1-1 conversation.', +}); + +async function sendEmojiReaction(platform: SupportedPlatformsType, testInfo: TestInfo) { + const message = 'Testing emoji reacts'; + const { + devices: { alice1, bob1 }, + prebuilt: { alice, bob }, + } = await test.step(TestSteps.SETUP.QA_SEEDER, async () => { + return open_Alice1_Bob1_friends({ + platform, + focusFriendsConvo: true, + testInfo, + }); + }); + await test.step(TestSteps.SEND.MESSAGE(alice.userName, bob.userName), async () => { + await alice1.sendMessage(message); + }); + await test.step(TestSteps.SEND.EMOJI_REACT, async () => { + await bob1.longPressMessage(message); + await bob1.clickOnElementAll(new FirstEmojiReact(bob1)); + // Verify long press menu disappeared (so next found emoji is in convo and not in react bar) + await bob1.verifyElementNotPresent({ + strategy: 'accessibility id', + selector: 'Reply to message', + }); + }); + await test.step(TestSteps.VERIFY.EMOJI_REACT, async () => { + await Promise.all( + [alice1, bob1].map(device => + device.waitForTextElementToBePresent(new EmojiReactsPill(device)) + ) + ); + }); + await test.step(TestSteps.SETUP.CLOSE_APP, async () => { + await closeApp(alice1, bob1); + }); +} diff --git a/run/test/specs/message_requests_accept.spec.ts b/run/test/specs/message_requests_accept.spec.ts index 4b64cafec..cdce72623 100644 --- a/run/test/specs/message_requests_accept.spec.ts +++ b/run/test/specs/message_requests_accept.spec.ts @@ -3,7 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; -import { MessageRequestsBanner } from './locators/home'; +import { ConversationItem, MessageRequestsBanner } from './locators/home'; import { newUser } from './utils/create_account'; import { linkedDevice } from './utils/link_device'; import { closeApp, openAppThreeDevices, SupportedPlatformsType } from './utils/open_app'; @@ -45,18 +45,11 @@ async function acceptRequest(platform: SupportedPlatformsType, testInfo: TestInf // Check conversation list for new contact (user A) await device2.navigateBack(); await device2.onAndroid().navigateBack(false); - await Promise.all([ - device2.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: alice.userName, - }), - device3.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: alice.userName, - }), - ]); + await Promise.all( + [device2, device3].map(device => + device.waitForTextElementToBePresent(new ConversationItem(device, alice.userName)) + ) + ); // Close app await closeApp(device1, device2, device3); } diff --git a/run/test/specs/message_requests_block.spec.ts b/run/test/specs/message_requests_block.spec.ts index e7414deaa..525dd27de 100644 --- a/run/test/specs/message_requests_block.spec.ts +++ b/run/test/specs/message_requests_block.spec.ts @@ -4,6 +4,7 @@ import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { type AccessibilityId, USERNAME } from '../../types/testing'; import { BlockedContactsSettings } from './locators'; +import { Contact } from './locators/global'; import { MessageRequestsBanner, PlusButton } from './locators/home'; import { ConversationsMenuItem, UserSettings } from './locators/settings'; import { sleepFor } from './utils'; @@ -52,7 +53,7 @@ async function blockedRequest(platform: SupportedPlatformsType, testInfo: TestIn strategy: 'accessibility id', selector: messageRequestsNonePending as AccessibilityId, }), - device3.hasElementBeenDeleted(new MessageRequestsBanner(device3)), + device3.verifyElementNotPresent({...new MessageRequestsBanner(device3).build(), maxWait: 5_000}), ]); const blockedMessage = `"${alice.userName} to ${bob.userName} - shouldn't get through"`; await device1.sendMessage(blockedMessage); @@ -62,33 +63,13 @@ async function blockedRequest(platform: SupportedPlatformsType, testInfo: TestIn await sleepFor(5000); await device2.hasTextElementBeenDeleted('Message body', blockedMessage); // Check that user is on Blocked User list in Settings - - await Promise.all([ - device2.clickOnElementAll(new UserSettings(device2)), - device3.clickOnElementAll(new UserSettings(device3)), - ]); - // 'Conversations' might be hidden beyond the Settings view, gotta scroll down to find it - await Promise.all([device2.scrollDown(), device3.scrollDown()]); - await Promise.all([ - device2.clickOnElementAll(new ConversationsMenuItem(device2)), - device3.clickOnElementAll(new ConversationsMenuItem(device3)), - ]); - await Promise.all([ - device2.clickOnElementAll(new BlockedContactsSettings(device2)), - device3.clickOnElementAll(new BlockedContactsSettings(device3)), - ]); - await Promise.all([ - device2.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Contact', - text: alice.userName, - }), - device3.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Contact', - text: alice.userName, - }), - ]); +await Promise.all( + [device2, device3].map(async (device) => { + await device.clickOnElementAll(new UserSettings(device)); + await device.clickOnElementAll(new ConversationsMenuItem(device)); + await device.clickOnElementAll(new BlockedContactsSettings(device)); + await device.waitForTextElementToBePresent(new Contact(device, alice.userName)); + })); // Close app await closeApp(device1, device2, device3); } diff --git a/run/test/specs/message_unsend.spec.ts b/run/test/specs/message_unsend.spec.ts index bd52940db..779d9ba40 100644 --- a/run/test/specs/message_unsend.spec.ts +++ b/run/test/specs/message_unsend.spec.ts @@ -3,7 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { DeleteMessageConfirmationModal, DeleteMessageForEveryone } from './locators'; -import { DeletedMessage } from './locators/conversation'; +import { DeletedMessage, MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -27,11 +27,7 @@ async function unsendMessage(platform: SupportedPlatformsType, testInfo: TestInf // send message from User A to User B const sentMessage = await alice1.sendMessage(testMessage); // await sleepFor(1000); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: sentMessage, - }); + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, sentMessage)); await alice1.longPressMessage(sentMessage); // Select Delete icon await alice1.clickOnByAccessibilityID('Delete message'); diff --git a/run/test/specs/message_video.spec.ts b/run/test/specs/message_video.spec.ts index f4d47b978..f2357122f 100644 --- a/run/test/specs/message_video.spec.ts +++ b/run/test/specs/message_video.spec.ts @@ -1,6 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -37,17 +38,9 @@ async function sendVideoIos(platform: SupportedPlatformsType, testInfo: TestInfo // User B - Click on untrusted attachment message await bob1.trustAttachments(alice.userName); // Reply to message - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }); + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, testMessage)); const replyMessage = await bob1.replyToMessage(alice, testMessage); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, replyMessage)); // Close app and server await closeApp(alice1, bob1); } @@ -76,12 +69,7 @@ async function sendVideoAndroid(platform: SupportedPlatformsType, testInfo: Test await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); await sleepFor(2000); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); - + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, replyMessage)); // Close app and server await closeApp(alice1, bob1); } diff --git a/run/test/specs/message_voice.spec.ts b/run/test/specs/message_voice.spec.ts index 9e9412a14..b786bc127 100644 --- a/run/test/specs/message_voice.spec.ts +++ b/run/test/specs/message_voice.spec.ts @@ -1,6 +1,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -35,11 +36,6 @@ async function sendVoiceMessage(platform: SupportedPlatformsType, testInfo: Test await bob1.longPress('Voice message'); await bob1.clickOnByAccessibilityID('Reply to message'); await bob1.sendMessage(replyMessage); - - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await alice1.waitForTextElementToBePresent(new MessageBody(alice1, replyMessage)); await closeApp(alice1, bob1); } diff --git a/run/test/specs/ons_resolve.spec.ts b/run/test/specs/ons_resolve.spec.ts index d9d270c57..b22edcb2b 100644 --- a/run/test/specs/ons_resolve.spec.ts +++ b/run/test/specs/ons_resolve.spec.ts @@ -42,7 +42,7 @@ async function resolveONS(platform: SupportedPlatformsType, testInfo: TestInfo) }); await test.step(`Verify ONS resolution to pubkey '${expectedPubkey}'`, async () => { await device.waitForTextElementToBePresent({ - ...new ConversationHeaderName(device).build(expectedPubkey), + ...new ConversationHeaderName(device, expectedPubkey).build(), maxWait: 5_000, }); }); diff --git a/run/test/specs/user_actions_block_conversation_list.spec.ts b/run/test/specs/user_actions_block_conversation_list.spec.ts index dfba6a7e2..37af68ce9 100644 --- a/run/test/specs/user_actions_block_conversation_list.spec.ts +++ b/run/test/specs/user_actions_block_conversation_list.spec.ts @@ -4,7 +4,8 @@ import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { androidIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; import { BlockedContactsSettings } from './locators'; -import { LongPressBlockOption } from './locators/home'; +import { Contact } from './locators/global'; +import { ConversationItem, LongPressBlockOption } from './locators/home'; import { ConversationsMenuItem, UserSettings } from './locators/settings'; import { open_Alice1_Bob1_friends } from './state_builder'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -43,20 +44,14 @@ async function blockUserInConversationList(platform: SupportedPlatformsType, tes await alice1.clickOnByAccessibilityID('Block'); // Once you block the conversation disappears from the home screen await alice1.verifyElementNotPresent({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: bob.userName, - maxWait: 5000, + ...new ConversationItem(alice1, bob.userName).build(), + maxWait: 5_000, }); await alice1.clickOnElementAll(new UserSettings(alice1)); // 'Conversations' might be hidden beyond the Settings view, gotta scroll down to find it await alice1.scrollDown(); await alice1.clickOnElementAll(new ConversationsMenuItem(alice1)); await alice1.clickOnElementAll(new BlockedContactsSettings(alice1)); - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Contact', - text: bob.userName, - }); + await alice1.waitForTextElementToBePresent(new Contact(alice1, bob.userName)); await closeApp(alice1, bob1); } diff --git a/run/test/specs/user_actions_block_conversation_options.spec.ts b/run/test/specs/user_actions_block_conversation_options.spec.ts index c8ef5175e..0c44cd58d 100644 --- a/run/test/specs/user_actions_block_conversation_options.spec.ts +++ b/run/test/specs/user_actions_block_conversation_options.spec.ts @@ -6,9 +6,10 @@ import { BlockedContactsSettings, BlockUser, BlockUserConfirmationModal, - ExitUserProfile, + CloseSettings, } from './locators'; -import { BlockedBanner, ConversationSettings } from './locators/conversation'; +import { BlockedBanner, ConversationSettings, MessageBody } from './locators/conversation'; +import { Contact } from './locators/global'; import { ConversationsMenuItem, UserSettings } from './locators/settings'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; @@ -73,21 +74,15 @@ async function blockUserInConversationSettings( await alice1.clickOnElementAll(new ConversationsMenuItem(alice1)); await alice1.clickOnElementAll(new BlockedContactsSettings(alice1)); // Accessibility ID for Blocked Contact not present on iOS - await alice1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Contact', - text: bob.userName, - }); + await alice1.waitForTextElementToBePresent(new Contact(alice1, bob.userName)); await alice1.navigateBack(false); await alice1.navigateBack(false); - await alice1.clickOnElementAll(new ExitUserProfile(alice1)); + await alice1.clickOnElementAll(new CloseSettings(alice1)); // Send message from Blocked User await bob1.sendMessage(blockedMessage); await alice1.verifyElementNotPresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: blockedMessage, - maxWait: 5000, + ...new MessageBody(alice1, blockedMessage).build(), + maxWait: 5_000, }); // Close app await closeApp(alice1, bob1); diff --git a/run/test/specs/user_actions_change_profile_picture.spec.ts b/run/test/specs/user_actions_change_profile_picture.spec.ts index 9aa6b4efa..bdca8f7fd 100644 --- a/run/test/specs/user_actions_change_profile_picture.spec.ts +++ b/run/test/specs/user_actions_change_profile_picture.spec.ts @@ -3,7 +3,7 @@ import { test, type TestInfo } from '@playwright/test'; import { TestSteps } from '../../types/allure'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; -import { UserSettings } from './locators/settings'; +import { UserAvatar } from './locators/settings'; import { newUser } from './utils/create_account'; import { closeApp, openAppOnPlatformSingleDevice, SupportedPlatformsType } from './utils/open_app'; @@ -31,10 +31,10 @@ async function changeProfilePicture(platform: SupportedPlatformsType, testInfo: await device.uploadProfilePicture(); }); await test.step(TestSteps.VERIFY.PROFILE_PICTURE_CHANGED, async () => { - await device.waitForElementColorMatch(new UserSettings(device), expectedPixelHexColor, { - maxWait: 10_000, - elementTimeout: 500, - }); + await device.waitForElementColorMatch( + { ...new UserAvatar(device).build(), maxWait: 10_000 }, + expectedPixelHexColor + ); }); await test.step(TestSteps.SETUP.CLOSE_APP, async () => { await closeApp(device); diff --git a/run/test/specs/user_actions_change_username.spec.ts b/run/test/specs/user_actions_change_username.spec.ts index 006316fc1..0401da51d 100644 --- a/run/test/specs/user_actions_change_username.spec.ts +++ b/run/test/specs/user_actions_change_username.spec.ts @@ -1,92 +1,39 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; -import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; -import { TickButton, UsernameInput, UsernameSettings } from './locators'; +import { ClearInputButton, EditUsernameButton, UsernameDisplay, UsernameInput } from './locators'; import { SaveNameChangeButton, UserSettings } from './locators/settings'; -import { sleepFor } from './utils'; import { newUser } from './utils/create_account'; import { closeApp, openAppOnPlatformSingleDevice, SupportedPlatformsType } from './utils/open_app'; -bothPlatformsItSeparate({ +bothPlatformsIt({ title: 'Change username', risk: 'medium', countOfDevicesNeeded: 1, - ios: { - testCb: changeUsernameiOS, - }, - android: { - testCb: changeUsernameAndroid, + testCb: changeUsername, + allureLinks: { + android: 'SES-4277', }, }); -async function changeUsernameiOS(platform: SupportedPlatformsType, testInfo: TestInfo) { +async function changeUsername(platform: SupportedPlatformsType, testInfo: TestInfo) { const { device } = await openAppOnPlatformSingleDevice(platform, testInfo); - const alice = await newUser(device, USERNAME.ALICE); + await newUser(device, USERNAME.ALICE); const newUsername = 'Alice in chains'; // click on settings/profile avatar await device.clickOnElementAll(new UserSettings(device)); // select username - await device.clickOnElementAll(new UsernameSettings(device)); - // New modal pops up + await device.clickOnElementAll(new EditUsernameButton(device)); await device.checkModalStrings( englishStrippedStr('displayNameSet').toString(), englishStrippedStr('displayNameVisible').toString() ); - // type in new username - await sleepFor(100); - await device.deleteText(new UsernameInput(device)); + await device.onIOS().deleteText(new UsernameInput(device)); + await device.onAndroid().clickOnElementAll(new ClearInputButton(device)); await device.inputText(newUsername, new UsernameInput(device)); await device.clickOnElementAll(new SaveNameChangeButton(device)); - const username = await device.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Username', - }); - const changedUsername = await device.getTextFromElement(username); - device.log('Changed username', changedUsername); - if (changedUsername === newUsername) { - device.log('Username change successful'); - } - if (changedUsername === alice.userName) { - throw new Error('Username change unsuccessful'); - } - await device.closeScreen(); - await closeApp(device); -} - -async function changeUsernameAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { - const { device } = await openAppOnPlatformSingleDevice(platform, testInfo); - const alice = await newUser(device, USERNAME.ALICE); - const newUsername = 'Alice in chains'; - // click on settings/profile avatar - await device.clickOnElementAll(new UserSettings(device)); - // select username - await device.clickOnElementAll(new UsernameSettings(device)); - // type in new username - await sleepFor(100); - await device.deleteText(new UsernameInput(device)); - await device.inputText(newUsername, new UsernameInput(device)); - await device.clickOnElementAll(new TickButton(device)); - const username = await device.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Display name', - text: newUsername, - }); - const changedUsername = await device.getTextFromElement(username); - device.log('Changed username', changedUsername); - if (changedUsername === newUsername) { - device.log('Username change successful'); - } - if (changedUsername === alice.userName) { - throw new Error('Username change unsuccessful'); - } - await device.closeScreen(); - await device.clickOnElementAll(new UserSettings(device)); - await device.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Display name', - text: newUsername, - }); + await device.waitForTextElementToBePresent(new UsernameDisplay(device, newUsername)); await closeApp(device); } diff --git a/run/test/specs/user_actions_create_contact.spec.ts b/run/test/specs/user_actions_create_contact.spec.ts index 86869ac5c..3dd56e1d1 100644 --- a/run/test/specs/user_actions_create_contact.spec.ts +++ b/run/test/specs/user_actions_create_contact.spec.ts @@ -2,7 +2,7 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; -import { MessageRequestsBanner } from './locators/home'; +import { ConversationItem, MessageRequestsBanner } from './locators/home'; import { newUser } from './utils/create_account'; import { retryMsgSentForBanner } from './utils/create_contact'; import { linkedDevice } from './utils/link_device'; @@ -48,42 +48,11 @@ async function createContact(platform: SupportedPlatformsType, testInfo: TestInf await device1.navigateBack(); await device2.navigateBack(); // Check username has changed from session id on both device 1 and 3 - await Promise.all([ - device1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: Bob.userName, - }), - device3.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: Bob.userName, - }), - ]); - // Check contact is added to contacts list on device 1 and 3 (linked device) - // await Promise.all([ - // device1.clickOnElementAll({ - // strategy: "accessibility id", - // selector: "New conversation button", - // }), - // device3.clickOnElementAll({ - // strategy: "accessibility id", - // selector: "New conversation button", - // }), - // ]); + await Promise.all( + [device1, device3].map(device => + device.waitForTextElementToBePresent(new ConversationItem(device, Bob.userName)) + ) + ); - // NEED CONTACT ACCESSIBILITY ID TO BE ADDED - // await Promise.all([ - // device1.waitForTextElementToBePresent({ - // strategy: "accessibility id", - // selector: "Contacts", - // }), - // device3.waitForTextElementToBePresent({ - // strategy: "accessibility id", - // selector: "Contacts", - // }), - // ]); - - // Wait for tick await closeApp(device1, device2, device3); } diff --git a/run/test/specs/user_actions_delete_contact_ucs.spec.ts b/run/test/specs/user_actions_delete_contact_ucs.spec.ts index 9071297ff..b6e34e506 100644 --- a/run/test/specs/user_actions_delete_contact_ucs.spec.ts +++ b/run/test/specs/user_actions_delete_contact_ucs.spec.ts @@ -8,6 +8,7 @@ import { ConversationSettings, DeleteContactConfirmButton, DeleteContactMenuItem, + MessageBody, } from './locators/conversation'; import { ConversationItem, MessageRequestsBanner } from './locators/home'; import { open_Alice2_Bob1_friends } from './state_builder'; @@ -78,11 +79,7 @@ async function deleteContactCS(platform: SupportedPlatformsType, testInfo: TestI [alice1, alice2].map(async device => { await device.clickOnElementAll(new MessageRequestsBanner(device)); await device.clickOnByAccessibilityID('Message request'); - await device.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: newMessage, - }); + await device.waitForTextElementToBePresent(new MessageBody(device, newMessage)); }) ); }); diff --git a/run/test/specs/user_actions_hide_recovery_password.spec.ts b/run/test/specs/user_actions_hide_recovery_password.spec.ts index ad9f1ac34..cd6e1fd3c 100644 --- a/run/test/specs/user_actions_hide_recovery_password.spec.ts +++ b/run/test/specs/user_actions_hide_recovery_password.spec.ts @@ -3,7 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; -import { ContinueButton } from './locators/global'; +import { AccountIDDisplay, ContinueButton } from './locators/global'; import { HideRecoveryPasswordButton, RecoveryPasswordMenuItem, @@ -49,10 +49,7 @@ async function hideRecoveryPassword(platform: SupportedPlatformsType, testInfo: }); // Should be taken back to Settings page after hiding recovery password await device1.onAndroid().scrollUp(); - await device1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Account ID', - }); + await device1.waitForTextElementToBePresent(new AccountIDDisplay(device1)); // Check that linked device still has Recovery Password await device2.clickOnElementAll(new UserSettings(device2)); await device2.scrollDown(); diff --git a/run/test/specs/user_actions_read_status.spec.ts b/run/test/specs/user_actions_read_status.spec.ts index 3a7676d1f..8ccc3d1cb 100644 --- a/run/test/specs/user_actions_read_status.spec.ts +++ b/run/test/specs/user_actions_read_status.spec.ts @@ -1,6 +1,8 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; +import { MessageBody } from './locators/conversation'; +import { ConversationItem } from './locators/home'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils/index'; import { closeApp, SupportedPlatformsType } from './utils/open_app'; @@ -25,24 +27,12 @@ async function readStatus(platform: SupportedPlatformsType, testInfo: TestInfo) // Go to settings to turn on read status // Device 1 await Promise.all([alice1.turnOnReadReceipts(), bob1.turnOnReadReceipts()]); - await alice1.clickOnElementAll({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: bob.userName, - }); + await alice1.clickOnElementAll(new ConversationItem(alice1, bob.userName)); // Send message from User A to User B to verify read status is working await alice1.sendMessage(testMessage); await sleepFor(100); - await bob1.clickOnElementAll({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: alice.userName, - }); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }); + await bob1.clickOnElementAll(new ConversationItem(bob1, alice.userName)); + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, testMessage)); // Check read status on device 1 await alice1.onAndroid().waitForTextElementToBePresent({ strategy: 'id', diff --git a/run/test/specs/user_actions_set_nickname.spec.ts b/run/test/specs/user_actions_set_nickname.spec.ts index 253ee7a4c..5ba9d51d6 100644 --- a/run/test/specs/user_actions_set_nickname.spec.ts +++ b/run/test/specs/user_actions_set_nickname.spec.ts @@ -26,6 +26,9 @@ bothPlatformsIt({ suite: 'Set Nickname', }, allureDescription: `Verifies that a user can set a nickname for a contact and that it appears correctly in the conversation settings, conversation header and home screen.`, + allureLinks: { + android: 'SES-4424', + }, }); async function setNickname(platform: SupportedPlatformsType, testInfo: TestInfo) { diff --git a/run/test/specs/user_actions_share_to_session.spec.ts b/run/test/specs/user_actions_share_to_session.spec.ts index 2585a19c7..b53fa5dd4 100644 --- a/run/test/specs/user_actions_share_to_session.spec.ts +++ b/run/test/specs/user_actions_share_to_session.spec.ts @@ -5,8 +5,9 @@ import { TestSteps } from '../../types/allure'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; import { ImageName, ShareExtensionIcon } from './locators'; -import { MessageInput, SendButton } from './locators/conversation'; +import { MessageBody, MessageInput, SendButton } from './locators/conversation'; import { PhotoLibrary } from './locators/external'; +import { Contact } from './locators/global'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils'; import { handlePhotosFirstTimeOpen } from './utils/handle_first_open'; @@ -27,6 +28,7 @@ bothPlatformsIt({ async function shareToSession(platform: SupportedPlatformsType, testInfo: TestInfo) { const { devices: { alice1, bob1 }, + prebuilt: { bob }, } = await test.step(TestSteps.SETUP.QA_SEEDER, async () => { return open_Alice1_Bob1_friends({ platform, @@ -58,11 +60,7 @@ async function shareToSession(platform: SupportedPlatformsType, testInfo: TestIn await alice1.onAndroid().clickOnElementAll(new ImageName(alice1)); await alice1.clickOnElementAll({ strategy: 'accessibility id', selector: 'Share' }); await alice1.clickOnElementAll(new ShareExtensionIcon(alice1)); - await alice1.clickOnElementAll({ - strategy: 'accessibility id', - selector: 'Contact', - text: USERNAME.BOB, - }); + await alice1.clickOnElementAll(new Contact(alice1, bob.userName)); await alice1.inputText(testMessage, new MessageInput(alice1)); await alice1.clickOnElementAll(new SendButton(alice1)); // Loading screen... @@ -72,11 +70,7 @@ async function shareToSession(platform: SupportedPlatformsType, testInfo: TestIn }); await test.step(TestSteps.VERIFY.MESSAGE_RECEIVED, async () => { await bob1.trustAttachments(USERNAME.ALICE); - await bob1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: testMessage, - }); + await bob1.waitForTextElementToBePresent(new MessageBody(bob1, testMessage)); }); await test.step(TestSteps.SETUP.CLOSE_APP, async () => { await closeApp(alice1, bob1); diff --git a/run/test/specs/user_actions_unblock_user.spec.ts b/run/test/specs/user_actions_unblock_user.spec.ts index d2d610f8e..537842469 100644 --- a/run/test/specs/user_actions_unblock_user.spec.ts +++ b/run/test/specs/user_actions_unblock_user.spec.ts @@ -3,7 +3,7 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; import { BlockUser, BlockUserConfirmationModal } from './locators'; -import { BlockedBanner, ConversationSettings } from './locators/conversation'; +import { BlockedBanner, ConversationSettings, MessageBody } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { SupportedPlatformsType } from './utils/open_app'; @@ -50,10 +50,8 @@ async function unblockUser(platform: SupportedPlatformsType, testInfo: TestInfo) // Send message from Blocked User await bob1.sendMessage(blockedMessage); await alice1.verifyElementNotPresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: blockedMessage, - maxWait: 5000, + ...new MessageBody(alice1, blockedMessage).build(), + maxWait: 5_000, }); // Now that user is blocked, unblock them await alice1.clickOnElementAll(new BlockedBanner(alice1)); diff --git a/run/test/specs/utils/allure/allureHelpers.ts b/run/test/specs/utils/allure/allureHelpers.ts index 2ccaa6225..eeabca5ad 100644 --- a/run/test/specs/utils/allure/allureHelpers.ts +++ b/run/test/specs/utils/allure/allureHelpers.ts @@ -1,3 +1,4 @@ +import * as allure from 'allure-js-commons'; import { execSync } from 'child_process'; import fs from 'fs-extra'; import { glob } from 'glob'; @@ -8,6 +9,7 @@ import { allureResultsDir, GH_PAGES_BASE_URL, } from '../../../../constants/allure'; +import { AllureSuiteConfig } from '../../../../types/allure'; import { SupportedPlatformsType } from '../open_app'; export interface ReportContext { @@ -177,3 +179,49 @@ function getGitCommitSha(): string { function getGitBranch(): string { return execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); } +// Handle test-level metadata such as suites, test description or linked issues +export async function setupAllureTestInfo({ + suites, + description, + links, + platform, +}: { + suites?: AllureSuiteConfig; + description?: string; + links?: { + all?: string[] | string; // Bugs affecting both platforms + android?: string[] | string; // Android only - won't appear in iOS reports + ios?: string[] | string; // iOS only - won't appear in Android reports + }; + platform?: 'android' | 'ios'; +}) { + // Handle suites + if (suites) { + await allure.parentSuite(suites.parent); + if ('suite' in suites) { + await allure.suite(suites.suite); + } + } + + // Handle description + if (description) { + await allure.description(description); + } + + // Handle links (only process if platform is provided) + if (links && platform) { + const allLinks = links.all ? (Array.isArray(links.all) ? links.all : [links.all]) : []; + + const platformLinks = links[platform] + ? Array.isArray(links[platform]) + ? links[platform] + : [links[platform]] + : []; + + const combinedLinks = [...allLinks, ...platformLinks]; + + for (const jiraKey of combinedLinks) { + await allure.link(`https://optf.atlassian.net/browse/${jiraKey}`, jiraKey, 'issue'); + } + } +} diff --git a/run/test/specs/utils/capabilities_ios.ts b/run/test/specs/utils/capabilities_ios.ts index 5d835f95d..87bf203bd 100644 --- a/run/test/specs/utils/capabilities_ios.ts +++ b/run/test/specs/utils/capabilities_ios.ts @@ -28,6 +28,7 @@ const sharediOSCapabilities: AppiumXCUITestCapabilities = { 'appium:processArguments': { env: { debugDisappearingMessageDurations: 'true', + communityPollLimit: 5, }, }, // "appium:isHeadless": true, diff --git a/run/test/specs/utils/create_account.ts b/run/test/specs/utils/create_account.ts index cfccd1237..853ad31fa 100644 --- a/run/test/specs/utils/create_account.ts +++ b/run/test/specs/utils/create_account.ts @@ -2,7 +2,8 @@ import type { UserNameType } from '@session-foundation/qa-seeder'; import { DeviceWrapper } from '../../../types/DeviceWrapper'; import { User } from '../../../types/testing'; -import { ContinueButton } from '../locators/global'; +import { CloseSettings } from '../locators'; +import { AccountIDDisplay, ContinueButton } from '../locators/global'; import { CreateAccountButton, DisplayNameInput, SlowModeRadio } from '../locators/onboarding'; import { RecoveryPhraseContainer, RevealRecoveryPhraseButton } from '../locators/settings'; import { UserSettings } from '../locators/settings'; @@ -56,7 +57,8 @@ export async function newUser( // Exit Modal await device.navigateBack(false); await device.clickOnElementAll(new UserSettings(device)); - const accountID = await device.grabTextFromAccessibilityId('Account ID'); - await device.closeScreen(false); + const el = await device.waitForTextElementToBePresent(new AccountIDDisplay(device)); + const accountID = await device.getTextFromElement(el); + await device.clickOnElementAll(new CloseSettings(device)); return { userName, accountID, recoveryPhrase }; } diff --git a/run/test/specs/utils/create_contact.ts b/run/test/specs/utils/create_contact.ts index 16cdbeb51..eeba536f2 100644 --- a/run/test/specs/utils/create_contact.ts +++ b/run/test/specs/utils/create_contact.ts @@ -1,6 +1,7 @@ import { runOnlyOnIOS, sleepFor } from '.'; import { DeviceWrapper } from '../../../types/DeviceWrapper'; import { User } from '../../../types/testing'; +import { MessageBody } from '../locators/conversation'; import { MessageRequestsBanner } from '../locators/home'; import { SupportedPlatformsType } from './open_app'; @@ -28,11 +29,7 @@ export const newContact = async ( // TO DO - ADD BACK IN ONCE IOS AND ANDROID HAS FIXED THIS ISSUE // const messageRequestsAccepted = englishStrippedStr('messageRequestsAccepted').toString(); // await device1.onAndroid().waitForControlMessageToBePresent(messageRequestsAccepted); - await device1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: replyMessage, - }); + await device1.waitForTextElementToBePresent(new MessageBody(device1, replyMessage)); console.info(`${sender.userName} and ${receiver.userName} are now contacts`); return { sender, receiver, device1, device2 }; }; diff --git a/run/test/specs/utils/create_group.ts b/run/test/specs/utils/create_group.ts index a2a29b575..3e0eae2f3 100644 --- a/run/test/specs/utils/create_group.ts +++ b/run/test/specs/utils/create_group.ts @@ -1,6 +1,7 @@ import { englishStrippedStr } from '../../../localizer/englishStrippedStr'; import { DeviceWrapper } from '../../../types/DeviceWrapper'; import { Group, GROUPNAME, User } from '../../../types/testing'; +import { MessageBody } from '../locators/conversation'; import { Contact } from '../locators/global'; import { CreateGroupButton, GroupNameInput } from '../locators/groups'; import { ConversationItem, PlusButton } from '../locators/home'; @@ -84,45 +85,24 @@ export const createGroup = async ( // Send message from User A to group to verify all working await device1.sendMessage(aliceMessage); // Did the other devices receive alice's message? - await Promise.all([ - device2.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: aliceMessage, - }), - device3.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: aliceMessage, - }), - ]); + await Promise.all( + [device2, device3].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, aliceMessage)) + ) + ); // Send message from User B to group await device2.sendMessage(bobMessage); - await Promise.all([ - device1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: bobMessage, - }), - device3.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: bobMessage, - }), - ]); + await Promise.all( + [device1, device3].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, bobMessage)) + ) + ); // Send message to User C to group await device3.sendMessage(charlieMessage); - await Promise.all([ - device1.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: charlieMessage, - }), - device2.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: charlieMessage, - }), - ]); + await Promise.all( + [device1, device2].map(device => + device.waitForTextElementToBePresent(new MessageBody(device, charlieMessage)) + ) + ); return { userName, userOne, userTwo, userThree }; }; diff --git a/run/test/specs/utils/disappearing_control_messages.ts b/run/test/specs/utils/disappearing_control_messages.ts index 28e796b38..ea2b22a4b 100644 --- a/run/test/specs/utils/disappearing_control_messages.ts +++ b/run/test/specs/utils/disappearing_control_messages.ts @@ -3,6 +3,7 @@ import type { UserNameType } from '@session-foundation/qa-seeder'; import { englishStrippedStr } from '../../../localizer/englishStrippedStr'; import { DeviceWrapper } from '../../../types/DeviceWrapper'; import { DisappearActions, DISAPPEARING_TIMES } from '../../../types/testing'; +import { ConversationItem } from '../locators/home'; import { SupportedPlatformsType } from './open_app'; export const checkDisappearingControlMessage = async ( @@ -47,11 +48,7 @@ export const checkDisappearingControlMessage = async ( } // Check if control messages are syncing from both user A and user B if (linkedDevice) { - await linkedDevice.clickOnElementAll({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: userNameB, - }); + await linkedDevice.clickOnElementAll(new ConversationItem(linkedDevice, userNameB)); await linkedDevice.waitForControlMessageToBePresent(disappearingMessagesSetYou); await linkedDevice.waitForControlMessageToBePresent(disappearingMessagesSetBob); } diff --git a/run/test/specs/utils/get_account_id.ts b/run/test/specs/utils/get_account_id.ts index bd1e5a0c3..4820217e2 100644 --- a/run/test/specs/utils/get_account_id.ts +++ b/run/test/specs/utils/get_account_id.ts @@ -1,18 +1,6 @@ -import { DeviceWrapper } from '../../../types/DeviceWrapper'; import { User } from '../../../types/testing'; import { SupportedPlatformsType } from './open_app'; -export const saveSessionIdIos = async (device: DeviceWrapper) => { - const selector = await device.grabTextFromAccessibilityId('Session ID generated'); - return selector; -}; - -export const getAccountId = async (device: DeviceWrapper) => { - const AccountId = await device.grabTextFromAccessibilityId('Account ID'); - - return AccountId; -}; - export function sortByPubkey(...users: Array) { return [...users] .sort((a, b) => a.accountID.localeCompare(b.accountID)) diff --git a/run/test/specs/utils/index.ts b/run/test/specs/utils/index.ts index cbc92334b..66e353af7 100644 --- a/run/test/specs/utils/index.ts +++ b/run/test/specs/utils/index.ts @@ -1,13 +1,5 @@ import { clickOnCoordinates } from './click_by_coordinates'; -import { getAccountId, saveSessionIdIos } from './get_account_id'; import { runOnlyOnAndroid, runOnlyOnIOS } from './run_on'; import { sleepFor } from './sleep_for'; -export { - sleepFor, - saveSessionIdIos, - getAccountId, - runOnlyOnIOS, - runOnlyOnAndroid, - clickOnCoordinates, -}; +export { sleepFor, runOnlyOnIOS, runOnlyOnAndroid, clickOnCoordinates }; diff --git a/run/test/specs/utils/join_community.ts b/run/test/specs/utils/join_community.ts index 1b845d6b4..a18102732 100644 --- a/run/test/specs/utils/join_community.ts +++ b/run/test/specs/utils/join_community.ts @@ -13,7 +13,5 @@ export const joinCommunity = async ( await device.clickOnElementAll(new JoinCommunityOption(device)); await device.inputText(communityLink, new CommunityInput(device)); await device.clickOnElementAll(new JoinCommunityButton(device)); - await device.waitForTextElementToBePresent( - new ConversationHeaderName(device).build(communityName) - ); + await device.waitForTextElementToBePresent(new ConversationHeaderName(device, communityName)); }; diff --git a/run/test/specs/voice_calls.spec.ts b/run/test/specs/voice_calls.spec.ts index 0aea8678b..cca058d41 100644 --- a/run/test/specs/voice_calls.spec.ts +++ b/run/test/specs/voice_calls.spec.ts @@ -3,6 +3,7 @@ import { test, type TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { TestSteps } from '../../types/allure'; import { bothPlatformsItSeparate } from '../../types/sessionIt'; +import { CloseSettings } from './locators'; import { CallButton, NotificationSettings, NotificationSwitch } from './locators/conversation'; import { open_Alice1_Bob1_friends } from './state_builder'; import { sleepFor } from './utils/index'; @@ -77,7 +78,7 @@ async function voiceCallIos(platform: SupportedPlatformsType, testInfo: TestInfo ); } }); - await alice1.closeScreen(); + await alice1.clickOnElementAll(new CloseSettings(alice1)); // Alice tries again, call is created but Bob still hasn't enabled their calls perms so this will fail await test.step(TestSteps.CALLS.INITIATE_CALL(alice.userName), async () => { await alice1.clickOnElementAll(new CallButton(alice1)); @@ -118,7 +119,7 @@ async function voiceCallIos(platform: SupportedPlatformsType, testInfo: TestInfo Retrying won't help - use a real device where you can manually enable the permission.` ); } - await bob1.closeScreen(); + await bob1.clickOnElementAll(new CloseSettings(bob1)); await alice1.clickOnElementAll(new CallButton(alice1)); await bob1.clickOnByAccessibilityID('Answer call'); await Promise.all( diff --git a/run/types/DeviceWrapper.ts b/run/types/DeviceWrapper.ts index 1320bfed0..643df2701 100644 --- a/run/types/DeviceWrapper.ts +++ b/run/types/DeviceWrapper.ts @@ -10,6 +10,7 @@ import * as sinon from 'sinon'; import { ChangeProfilePictureButton, + CloseSettings, describeLocator, DownloadMediaButton, FirstGif, @@ -28,17 +29,19 @@ import { import { englishStrippedStr } from '../localizer/englishStrippedStr'; import { AttachmentsButton, + MessageBody, MessageInput, OutgoingMessageStatusSent, ScrollToBottomButton, SendButton, } from '../test/specs/locators/conversation'; -import { ModalDescription, ModalHeading } from '../test/specs/locators/global'; -import { PlusButton } from '../test/specs/locators/home'; +import { Contact, ModalDescription, ModalHeading } from '../test/specs/locators/global'; +import { ConversationItem, PlusButton } from '../test/specs/locators/home'; import { LoadingAnimation } from '../test/specs/locators/onboarding'; import { PrivacyMenuItem, SaveProfilePictureButton, + UserAvatar, UserSettings, } from '../test/specs/locators/settings'; import { @@ -310,15 +313,18 @@ export class DeviceWrapper { Array >; } + /** - * Attempts to click an element using a primary locator, and if not found, falls back to a secondary locator. + * Attempts to find an element using a primary locator, and if not found, falls back to a secondary locator. * This is useful for supporting UI transitions (e.g., between legacy and Compose Android screens) where - * the same UI element may have different locators depending context. + * the same UI element may have different locators depending on context. * - * @param primaryLocator - The first locator to try (e.g., new Compose locator or legacy locator). - * @param fallbackLocator - The locator to try if the primary is not found. + * @param primaryLocator - The first locator to try (e.g., new Compose locator). + * @param fallbackLocator - The locator to try if the primary is not found (e.g., legacy locator). * @param maxWait - Maximum wait time in milliseconds for each locator (default: 3000). - * @throws If neither locator is found. + * @returns The found element, which can be used for clicking, text extraction, or other operations. + * @throws If neither locator finds an element within the timeout period. + * */ public async findWithFallback( primaryLocator: LocatorsInterface | StrategyExtractionObj, @@ -329,21 +335,23 @@ export class DeviceWrapper { primaryLocator instanceof LocatorsInterface ? primaryLocator.build() : primaryLocator; const fallback = fallbackLocator instanceof LocatorsInterface ? fallbackLocator.build() : fallbackLocator; - let found = await this.doesElementExist({ ...primary, maxWait }); - if (found) { - await this.clickOnElementAll(primary); - return found; - } - console.warn( - `[findWithFallback] Could not find primary locator with '${primary.strategy}', falling back on '${fallback.strategy}'` - ); - found = await this.doesElementExist({ ...fallback, maxWait }); - if (found) { - await this.clickOnElementAll(fallback); - return found; + const primaryDescription = describeLocator(primary); + const fallbackDescription = describeLocator(fallback); + + try { + return await this.waitForTextElementToBePresent({ ...primary, maxWait }); + } catch (primaryError) { + console.warn( + `[findWithFallback] Could not find element with ${primaryDescription}, falling back to ${fallbackDescription}` + ); + + try { + return await this.waitForTextElementToBePresent({ ...fallback, maxWait }); + } catch (fallbackError) { + throw new Error(`Element ${primaryDescription} and ${fallbackDescription} not found.`); + } } - throw new Error(`[findWithFallback] Could not find primary or fallback locator`); } public async longClick(element: AppiumNextElementType, durationMs: number) { @@ -472,10 +480,8 @@ export class DeviceWrapper { while (attempt < maxRetries && !success) { try { const el = await this.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: textToLookFor, - maxWait: 1000, + ...new MessageBody(this, textToLookFor).build(), + maxWait: 1_000, }); if (!el) { throw new Error( @@ -516,11 +522,7 @@ export class DeviceWrapper { while (attempt < maxRetries && !success) { try { - const el = await this.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: userName, - }); + const el = await this.waitForTextElementToBePresent(new ConversationItem(this, userName)); if (!el) { throw new Error( @@ -730,9 +732,6 @@ export class DeviceWrapper { const matching = await this.findAsync(elements, async e => { const text = await this.getTextFromElement(e); const isPartialMatch = text && text.toLowerCase().includes(textToLookFor.toLowerCase()); - if (isPartialMatch) { - this.info(`Text found to include ${textToLookFor}`); - } return Boolean(isPartialMatch); }); @@ -769,12 +768,7 @@ export class DeviceWrapper { } public async findMessageWithBody(textToLookFor: string): Promise { - await this.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Message body', - text: textToLookFor, - }); - + await this.waitForTextElementToBePresent(new MessageBody(this, textToLookFor)); const message = await this.findMatchingTextAndAccessibilityId('Message body', textToLookFor); return message; } @@ -892,7 +886,7 @@ export class DeviceWrapper { } } catch (err) { // If matching fails for this element, log and continue to the next - this.warn( + this.info( `[matchAndTapImage] Matching failed for element ${i + 1}:`, err instanceof Error ? err.message : err ); @@ -933,7 +927,7 @@ export class DeviceWrapper { } /** - * Ensures an element is not present on the screen at the end of the wait time. + * Ensures an element is not visible on the screen at the end of the wait time. * This allows any transitions to complete and tolerates some UI flakiness. * Unlike hasElementBeenDeleted, this doesn't require the element to exist first. * @@ -960,14 +954,24 @@ export class DeviceWrapper { const description = describeLocator({ ...locator, text: args.text }); - if (element) { - throw new Error( - `Element with ${description} is present after ${maxWait}ms when it should not be` - ); + if (element) { + // Elements can disappear in the GUI but still be present in the DOM + try { + const isVisible = await this.isVisible(element.ELEMENT); + if (isVisible) { + throw new Error( + `Element with ${description} is visible after ${maxWait}ms when it should not be` + ); + } + // Element exists but not visible - that's okay + this.log(`Element with ${description} exists but is not visible`); + } catch (e) { + // Stale element or other error - element is gone, that's okay + this.log(`Element with ${description} is not present (stale reference)`); + } + } else { + this.log(`Verified no element with ${description} is present`); } - - // Element not found - success! - this.log(`Verified no element with ${description} is present`); } /** @@ -1180,7 +1184,11 @@ export class DeviceWrapper { args: { text?: string; maxWait?: number } & (LocatorsInterface | StrategyExtractionObj) ): Promise { const locator = args instanceof LocatorsInterface ? args.build() : args; - const { text, maxWait = 30_000 } = args; + + // Prefer text from args (if passed directly), otherwise check locator + const text = args.text ?? ('text' in locator ? locator.text : undefined); + + const { maxWait = 30_000 } = args; const description = describeLocator({ ...locator, text }); this.log(`Waiting for element with ${description} to be present`); @@ -1222,6 +1230,7 @@ export class DeviceWrapper { text: string, maxWait = 15000 ): Promise { + this.log(`Waiting for control message "${text}" to be present`); const result = await this.pollUntil( async () => { try { @@ -1230,11 +1239,11 @@ export class DeviceWrapper { return element ? { success: true, data: element } - : { success: false, error: `Control message with text "${text}" not found` }; + : { success: false, error: `Control message "${text}" not found` }; } catch (err) { return { success: false, - error: err instanceof Error ? err.message : String(err), + error: `Control message "${text}" not found`, }; } }, @@ -1242,7 +1251,7 @@ export class DeviceWrapper { ); if (!result) { - throw new Error(`Control message "${text}" not found after ${maxWait}ms`); + throw new Error(`Waited too long for control message "${text}"`); } this.log(`Control message "${text}" has been found`); @@ -1324,69 +1333,39 @@ export class DeviceWrapper { } } while (elapsed < maxWait); // Log the error with details but only throw generic error so that they get grouped in the report - this.error(`${lastError} after ${attempt} attempts (${elapsed}ms)`); + this.log(`${lastError} after ${attempt} attempts (${elapsed}ms)`); throw new Error(lastError || 'Polling failed'); } - - /** - * Wait for an element to meet a specific condition - */ - async waitForElementCondition( - args: { text?: string; maxWait?: number } & (LocatorsInterface | StrategyExtractionObj), - checkElement: (element: AppiumNextElementType) => Promise>, - options: { - maxWait?: number; - elementTimeout?: number; - } = {} - ): Promise { - const { elementTimeout = 500 } = options; - return this.pollUntil(async () => { - try { - // Convert to StrategyExtractionObj if needed - const locator = args instanceof LocatorsInterface ? args.build() : args; - - // Create new args with short timeout for polling - const pollArgs = { - ...locator, - text: args.text, - maxWait: elementTimeout, // Short timeout for each poll attempt - }; - - const element = await this.waitForTextElementToBePresent(pollArgs); - return await checkElement(element); - } catch (error) { - return { - success: false, - error: `Element not found: ${error instanceof Error ? error.message : String(error)}`, - }; - } - }, options); - } /** * Waits for an element's screenshot to match a specific color. * - * @param args - Element locator + * @param args - Element locator with optional text and maxWait * @param expectedColor - Hex color code (e.g., '04cbfe') - * @param options - Optional timeouts: maxWait (total) and elementTimeout (per check) * @throws If color doesn't match within timeout - * */ + public async waitForElementColorMatch( args: { text?: string; maxWait?: number } & (LocatorsInterface | StrategyExtractionObj), - expectedColor: string, - options: { - maxWait?: number; - elementTimeout?: number; - } = {} + expectedColor: string ): Promise { - await this.waitForElementCondition( - args, - async (element): Promise => { - // Capture screenshot of the element as base64 + const locator = args instanceof LocatorsInterface ? args.build() : args; + const description = describeLocator({ ...locator, text: args.text }); + + this.log(`Waiting for ${description} to have color #${expectedColor}`); + + await this.pollUntil( + async () => { + const element = await this.findElementQuietly(locator, args.text); + + if (!element) { + return { + success: false, + error: `Element not found`, + }; + } + const base64 = await this.getElementScreenshot(element.ELEMENT); - // Extract the middle pixel color from the screenshot const actualColor = await parseDataImage(base64); - // Compare colors using the standard color matcher const matches = isSameColor(expectedColor, actualColor); return { @@ -1396,10 +1375,11 @@ export class DeviceWrapper { : `Color mismatch: expected #${expectedColor}, got #${actualColor}`, }; }, - options + { + maxWait: args.maxWait, // Will use default from pollUntil if undefined + } ); } - // UTILITY FUNCTIONS public async sendMessage(message: string) { @@ -1470,17 +1450,7 @@ export class DeviceWrapper { public async sendMessageTo(sender: User, receiver: Group | User) { const message = `${sender.userName} to ${receiver.userName}`; - await this.waitForTextElementToBePresent({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: receiver.userName, - }); - await sleepFor(100); - await this.clickOnElementAll({ - strategy: 'accessibility id', - selector: 'Conversation list item', - text: receiver.userName, - }); + await this.clickOnElementAll(new ConversationItem(this, receiver.userName)); this.log(`${sender.userName} + " sent message to ${receiver.userName}`); await this.sendMessage(message); this.log(`Message received by ${receiver.userName} from ${sender.userName}`); @@ -1741,7 +1711,7 @@ export class DeviceWrapper { public async sendDocument() { if (this.isIOS()) { const formattedFileName = 'test_file, pdf'; - const testMessage = 'Testing-document-1'; + const testMessage = 'Testing documents'; copyFileToSimulator(this, testFile); await this.clickOnElementAll(new AttachmentsButton(this)); const keyboard = await this.isKeyboardVisible(); @@ -1873,7 +1843,7 @@ export class DeviceWrapper { public async uploadProfilePicture() { await this.clickOnElementAll(new UserSettings(this)); // Click on Profile picture - await this.clickOnElementAll(new UserSettings(this)); + await this.clickOnElementAll(new UserAvatar(this)); await this.clickOnElementAll(new ChangeProfilePictureButton(this)); if (this.isIOS()) { // Push file first @@ -1941,11 +1911,7 @@ export class DeviceWrapper { text: contact.userName, }); } else { - await this.clickOnElementAll({ - strategy: 'accessibility id', - selector: 'Contact', - text: contact.userName, - }); + await this.clickOnElementAll(new Contact(this, contact.userName)); } await this.clickOnElementAll(new SendButton(this)); await this.waitForTextElementToBePresent(new OutgoingMessageStatusSent(this)); @@ -2043,15 +2009,16 @@ export class DeviceWrapper { await this.scroll({ x: width / 2, y: height * 0.95 }, { x: width / 2, y: height * 0.35 }, 100); } + public async scrollToBottom() { try { const scrollButton = await this.waitForTextElementToBePresent({ ...new ScrollToBottomButton(this).build(), - maxWait: 1_000, + maxWait: 3_000, }); await this.click(scrollButton.ELEMENT); } catch { - this.info('Scroll button not found after 1s, continuing'); + this.info('Scroll button not found, continuing'); } } @@ -2088,32 +2055,8 @@ export class DeviceWrapper { const [primary, fallback] = newAndroid ? [newLocator, legacyLocator] : [legacyLocator, newLocator]; - await this.findWithFallback(primary, fallback); - } - } - - public async closeScreen(newAndroid: boolean = true) { - if (this.isIOS()) { - await this.clickOnByAccessibilityID('Close button'); - return; - } - - if (this.isAndroid()) { - const newLocator = { - strategy: 'id', - selector: 'Close button', - } as StrategyExtractionObj; - - const legacyLocator = { - strategy: 'accessibility id', - selector: 'Navigate up', - } as StrategyExtractionObj; - - const [primary, fallback] = newAndroid - ? [newLocator, legacyLocator] - : [legacyLocator, newLocator]; - - await this.findWithFallback(primary, fallback); + const el = await this.findWithFallback(primary, fallback); + await this.click(el.ELEMENT); } } @@ -2137,7 +2080,7 @@ export class DeviceWrapper { await this.clickOnElementAll(new ReadReceiptsButton(this)); await this.navigateBack(false); await sleepFor(100); - await this.closeScreen(false); + await this.clickOnElementAll(new CloseSettings(this)); } public async processPermissions(locator: LocatorsInterface) { @@ -2306,13 +2249,20 @@ export class DeviceWrapper { } await this.clickOnElementAll(new UserSettings(this)); - await this.scrollDown(); - const versionElement = await this.findElement( - 'id', - 'network.loki.messenger.qa:id/versionTextView' - ); + // Find the element using UiScrollable + const versionElement = await this.waitForTextElementToBePresent({ + strategy: '-android uiautomator', + selector: + 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().textStartsWith("Version"))', + }); + + // Get the full text from the element const versionText = await this.getAttribute('text', versionElement.ELEMENT); + // versionText will be something like "Version 1.27.0 (4175 - ac77d8) - Mainnet" + + // Extract just the version number const match = versionText?.match(/(\d+\.\d+\.\d+)/); + // match[1] will be "1.27.0" if (!match) { throw new Error(`Could not extract version from: ${versionText}`); diff --git a/run/types/allure.ts b/run/types/allure.ts index 1dfd195c9..5d5157914 100644 --- a/run/types/allure.ts +++ b/run/types/allure.ts @@ -25,7 +25,7 @@ export type AllureSuiteConfig = | { parent: 'In-App Review Prompt'; suite: 'Flows' | 'Triggers' } | { parent: 'Linkouts' } | { parent: 'New Conversation'; suite: 'Join Community' | 'New Message' } - | { parent: 'Sending Messages'; suite: 'Sending Attachments' } + | { parent: 'Sending Messages'; suite: 'Attachments' | 'Emoji reacts' } | { parent: 'Settings'; suite: 'App Disguise' } | { parent: 'User Actions'; @@ -68,17 +68,22 @@ export const TestSteps = { }, // Sending things SEND: { + MESSAGE: (sender: UserNameType, recipient: string) => + `${sender} sends a message to ${recipient}`, LINK: 'Send Link', IMAGE: 'Send Image', + EMOJI_REACT: `Send an emoji react`, }, // Open/Navigate steps OPEN: { UPDATE_GROUP_INFO: `Open 'Update Group Information' modal`, PATH: 'Open Path screen', + APPEARANCE: 'Open Appearance settings', }, // User Actions USER_ACTIONS: { CHANGE_PROFILE_PICTURE: 'Change profile picture', + APP_DISGUISE: 'Set App Disguise', }, // Disappearing Messages DISAPPEARING_MESSAGES: { @@ -90,6 +95,8 @@ export const TestSteps = { }, // Verify steps VERIFY: { + ELEMENT_SCREENSHOT: (elementDesc: string) => + `Verify ${elementDesc} element screenshot matches baseline`, GENERIC_MODAL: 'Verify modal strings', SPECIFIC_MODAL: (modalDesc: string) => `Verify ${modalDesc} modal strings`, MESSAGE_RECEIVED: 'Verify message has been received', @@ -99,5 +106,6 @@ export const TestSteps = { MISSED_CALL: 'Verify missed call', NICKNAME_CHANGED: (context: string) => `Verify nickname changed in/on ${context}`, PROFILE_PICTURE_CHANGED: 'Verify profile picture has been changed', + EMOJI_REACT: 'Verify emoji react appears for everyone', }, }; diff --git a/run/types/sessionIt.ts b/run/types/sessionIt.ts index 81dd43a65..5e56972be 100644 --- a/run/types/sessionIt.ts +++ b/run/types/sessionIt.ts @@ -1,10 +1,10 @@ // run/types/sessionIt.ts - Clean version matching original pattern import { test, type TestInfo } from '@playwright/test'; -import * as allure from 'allure-js-commons'; import { omit } from 'lodash'; import type { AppCountPerTest } from '../test/specs/state_builder'; +import { setupAllureTestInfo } from '../test/specs/utils/allure/allureHelpers'; import { getNetworkTarget } from '../test/specs/utils/devnet'; import { SupportedPlatformsType } from '../test/specs/utils/open_app'; import { @@ -24,6 +24,11 @@ type MobileItArgs = { shouldSkip?: boolean; allureSuites?: AllureSuiteConfig; allureDescription?: string; + allureLinks?: { + all?: string[] | string; + android?: string[] | string; + ios?: string[] | string; + }; }; export function androidIt(args: Omit) { @@ -43,6 +48,7 @@ function mobileIt({ countOfDevicesNeeded, allureSuites, allureDescription, + allureLinks, }: MobileItArgs) { const testName = `${title} @${platform} @${risk ?? 'default'}-risk @${countOfDevicesNeeded}-devices`; @@ -58,15 +64,14 @@ function mobileIt({ getNetworkTarget(platform); console.info(`\n\n==========> Running "${testName}"\n\n`); - if (allureSuites) { - await allure.parentSuite(allureSuites.parent); - if ('suite' in allureSuites) { - await allure.suite(allureSuites.suite); - } - } - if (allureDescription) { - await allure.description(allureDescription); - } + // Handle Suites, Descriptions and Links + await setupAllureTestInfo({ + suites: allureSuites, + description: allureDescription, + links: allureLinks, + platform, + }); + let testFailed = false; try { diff --git a/run/types/testing.ts b/run/types/testing.ts index 180381ced..7150b1886 100644 --- a/run/types/testing.ts +++ b/run/types/testing.ts @@ -57,7 +57,7 @@ export const InteractionPoints: Record = { BackToSession: { x: 42, y: 42 }, }; -export type Strategy = 'accessibility id' | 'class name' | 'id' | 'xpath'; +export type Strategy = '-android uiautomator' | 'accessibility id' | 'class name' | 'id' | 'xpath'; export type ConversationType = '1:1' | 'Community' | 'Group' | 'Note to Self'; @@ -97,6 +97,11 @@ export type DisappearOptsGroup = [ export type MergedOptions = DisappearOpts1o1 | DisappearOptsGroup; export type StrategyExtractionObj = + | { + strategy: Extract; + selector: UiAutomatorQuery; + text?: string; + } | { strategy: Extract; selector: AccessibilityId; @@ -148,9 +153,21 @@ export type XPath = | `/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/androidx.appcompat.widget.LinearLayoutCompat/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.TextView[2]` | `/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.ScrollView/android.widget.TabHost/android.widget.LinearLayout/android.widget.FrameLayout/androidx.viewpager.widget.ViewPager/android.widget.RelativeLayout/android.widget.GridView/android.widget.LinearLayout/android.widget.LinearLayout[2]`; +export type UiAutomatorQuery = + | 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().resourceId("Appearance"))' + | 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().resourceId("Conversations"))' + | 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().resourceId("path-menu-item"))' + | 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().text("Select app icon"))' + | 'new UiScrollable(new UiSelector().className("android.widget.ScrollView")).scrollIntoView(new UiSelector().textStartsWith("Version"))' + | 'new UiSelector().text("Enter your display name")' + | `new UiSelector().resourceId("Conversation header name").childSelector(new UiSelector().resourceId("pro-badge-text"))` + | `new UiSelector().text(${string})`; + export type AccessibilityId = | DISAPPEARING_TIMES | UserNameType + | '😂' + | '2' | 'Accept message request' | 'Accept name change' | 'Account ID' @@ -184,7 +201,6 @@ export type AccessibilityId = | 'Clear all' | 'Close' | 'Close button' - | 'Community input' | 'Community invitation' | 'Configuration message' | 'Confirm' @@ -282,6 +298,7 @@ export type AccessibilityId = | 'Manage Members' | 'Media message' | 'MeetingSE' + | 'Meetings option' | 'Mentions list' | 'Message body' | 'Message composition' @@ -378,6 +395,7 @@ export type AccessibilityId = | 'Voice message' | 'X' | 'Yes' + | 'You have changed the icon for “Session”.' | 'Your message request has been accepted.' | `${DISAPPEARING_TIMES} - Radio` | `${GROUPNAME}` @@ -398,6 +416,7 @@ export type Id = | 'Call' | 'clear-input-button-description' | 'clear-input-button-name' + | 'clear-input-button' | 'Close button' | 'com.android.chrome:id/negative_button' | 'com.android.chrome:id/signin_fre_dismiss_button' @@ -409,6 +428,7 @@ export type Id = | 'com.android.settings:id/switch_text' | 'com.google.android.apps.photos:id/sign_in_button' | 'com.google.android.apps.photos:id/text' + | 'Community input' | 'Confirm invite button' | 'Contact' | 'Contact status' @@ -438,7 +458,6 @@ export type Id = | 'Enter display name' | 'error-message' | 'group-description' - | 'group-name' | 'Group name' | 'Group name input' | 'hide-nts-confirm-button' @@ -474,12 +493,15 @@ export type Id = | 'network.loki.messenger.qa:id/crop_image_menu_crop' | 'network.loki.messenger.qa:id/emptyStateContainer' | 'network.loki.messenger.qa:id/endCallButton' + | 'network.loki.messenger.qa:id/layout_emoji_container' | 'network.loki.messenger.qa:id/linkPreviewView' | 'network.loki.messenger.qa:id/mediapicker_folder_item_thumbnail' | 'network.loki.messenger.qa:id/mediapicker_image_item_thumbnail' | 'network.loki.messenger.qa:id/messageStatusTextView' | 'network.loki.messenger.qa:id/openGroupTitleTextView' | 'network.loki.messenger.qa:id/play_overlay' + | 'network.loki.messenger.qa:id/reaction_1' + | 'network.loki.messenger.qa:id/reactions_pill_count' | 'network.loki.messenger.qa:id/scrollToBottomButton' | 'network.loki.messenger.qa:id/search_cancel' | 'network.loki.messenger.qa:id/search_result_title' @@ -501,6 +523,7 @@ export type Id = | 'preferred-display-name' | 'Privacy' | 'Privacy Policy' + | 'pro-badge-text' | 'Quit' | 'rate-app-button' | 'Recovery password container' @@ -524,6 +547,8 @@ export type Id = | 'update-group-info-confirm-button' | 'update-group-info-description-input' | 'update-group-info-name-input' + | 'update-username-confirm-button' + | 'User settings' | 'Version warning banner' | 'Yes' | `All ${AppName} notifications`