From d6dc52b6eadade991846c61e748d09a6f2b0ef78 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 29 Aug 2025 18:15:05 +0300 Subject: Improve e2e tests (#8927) * get e2e image picker working * verify create account actually reaches onboarding * wait for image to actually be attached before posting * wait until login finishes before moving on * sign out before switch accounts then wait until logged in * disable onboarding experiments in e2e * add testId to handle availability checkmark * fix too long username * update thread muting test to reflect current behaviour * hackfix for the british english translation * unflake the onboarding tests * fix curate list flow * admit defeat on the most list one --- __e2e__/flows/composer-self-label.yml | 12 +++++-- __e2e__/flows/composer.yml | 23 ++++++++++--- __e2e__/flows/create-account.yml | 5 ++- __e2e__/flows/curate-lists.yml | 12 ++++--- __e2e__/flows/mod-lists.yml | 20 +++++------ __e2e__/flows/onboarding-avatar-creator.yml | 16 +++++---- __e2e__/flows/onboarding.yml | 16 +++++---- __e2e__/flows/post-report-flow.yml | 5 ++- __e2e__/flows/profile-screen.yml | 8 ++--- __e2e__/flows/search-screen.yml | 10 +++--- __e2e__/flows/shared-prefs.yml | 6 +++- __e2e__/flows/thread-muting.yml | 47 +++++++++++++++++++++----- __e2e__/mock-server.ts | 24 ++++++------- __e2e__/setupServer.js | 4 +-- src/lib/media/picker.e2e.tsx | 17 ++++++++++ src/lib/media/picker.shared.ts | 22 ++++++++++++ src/lib/media/picker.tsx | 6 +++- src/lib/media/picker.web.tsx | 2 +- src/screens/Onboarding/StepFinished.tsx | 1 + src/screens/Onboarding/StepInterests/index.tsx | 1 + src/screens/Onboarding/StepProfile/index.tsx | 2 ++ src/screens/Onboarding/index.tsx | 6 ++-- src/screens/Signup/StepHandle/index.tsx | 5 ++- src/view/com/composer/SelectMediaButton.tsx | 22 +++--------- 24 files changed, 200 insertions(+), 92 deletions(-) diff --git a/__e2e__/flows/composer-self-label.yml b/__e2e__/flows/composer-self-label.yml index b91ff7507..4539e1e04 100644 --- a/__e2e__/flows/composer-self-label.yml +++ b/__e2e__/flows/composer-self-label.yml @@ -3,20 +3,26 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: ?users + SERVER_PATH: ?users - runFlow: file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" # Post an image with the porn label - assertVisible: - id: "composeFAB" + id: "composeFAB" - tapOn: id: "composeFAB" - inputText: "Post with an image" - tapOn: - id: "openGalleryBtn" + id: "openMediaBtn" +- extendedWaitUntil: + visible: + id: "selectedPhotosView" - tapOn: "Content warnings" - tapOn: "Porn" - tapOn: diff --git a/__e2e__/flows/composer.yml b/__e2e__/flows/composer.yml index d6cf7dff5..62f195de7 100644 --- a/__e2e__/flows/composer.yml +++ b/__e2e__/flows/composer.yml @@ -3,13 +3,17 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: ?users + SERVER_PATH: ?users - runFlow: file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" + - assertVisible: - id: "composeFAB" + id: "composeFAB" - tapOn: id: "composeFAB" - inputText: "Post text only" @@ -21,7 +25,10 @@ appId: xyz.blueskyweb.app id: "composeFAB" - inputText: "Post with an image" - tapOn: - id: "openGalleryBtn" + id: "openMediaBtn" +- extendedWaitUntil: + visible: + id: "selectedPhotosView" - tapOn: id: "composerPublishBtn" - assertVisible: @@ -46,7 +53,10 @@ appId: xyz.blueskyweb.app id: "replyBtn" - inputText: "Reply with an image" - tapOn: - id: "openGalleryBtn" + id: "openMediaBtn" +- extendedWaitUntil: + visible: + id: "selectedPhotosView" - tapOn: id: "composerPublishBtn" - assertVisible: @@ -73,7 +83,10 @@ appId: xyz.blueskyweb.app id: "quoteBtn" - inputText: "QP with an image" - tapOn: - id: "openGalleryBtn" + id: "openMediaBtn" +- extendedWaitUntil: + visible: + id: "selectedPhotosView" - tapOn: id: "composerPublishBtn" - assertVisible: diff --git a/__e2e__/flows/create-account.yml b/__e2e__/flows/create-account.yml index 4d49e9b6a..1290c94bf 100644 --- a/__e2e__/flows/create-account.yml +++ b/__e2e__/flows/create-account.yml @@ -32,6 +32,9 @@ appId: xyz.blueskyweb.app text: "Not Now" optional: true - inputText: "e2e-test" +- extendedWaitUntil: + visible: + id: "handleAvailableCheck" - tapOn: id: "nextBtn" - +- assertVisible: "Give your profile a face" diff --git a/__e2e__/flows/curate-lists.yml b/__e2e__/flows/curate-lists.yml index fdb71ae7e..8fe6a2c44 100644 --- a/__e2e__/flows/curate-lists.yml +++ b/__e2e__/flows/curate-lists.yml @@ -3,11 +3,14 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: "?users&follows&posts" + SERVER_PATH: "?users&follows&posts" - runFlow: file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" - tapOn: label: "Create a curate list" @@ -75,9 +78,8 @@ appId: xyz.blueskyweb.app - tapOn: id: "confirmBtn" -- tapOn: - label: "Create a new curatelist" - id: "e2eGotoLists" +- assertVisible: + id: "newUserListBtn" - tapOn: id: "newUserListBtn" - assertVisible: @@ -146,7 +148,7 @@ appId: xyz.blueskyweb.app - tapOn: id: "bottomBarSearchBtn" -- tapOn: "Search for posts, users, or feeds" +- tapOn: "Search for posts, users[,]? or feeds" - inputText: "bob" - tapOn: id: "searchAutoCompleteResult-bob.test" diff --git a/__e2e__/flows/mod-lists.yml b/__e2e__/flows/mod-lists.yml index ef757c5b1..02d76b83e 100644 --- a/__e2e__/flows/mod-lists.yml +++ b/__e2e__/flows/mod-lists.yml @@ -3,11 +3,14 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: "?users&follows&labels" + SERVER_PATH: "?users&follows&labels" - runFlow: file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" # create a modlist - tapOn: @@ -28,20 +31,17 @@ appId: xyz.blueskyweb.app - assertVisible: "Muted Users" - assertVisible: "Shhh" -- tapOn: - label: "Dropdown" - point: "71%,9%" - +# DOES NOT WORK - THE BUTTON IS NOT ACCESSIBLE +# IGNORING FOR NOW, FIX THE COMPONENTS IN THE NEXT RELEASE +# BECAUSE THIS IS A LEGIT A11Y PROBLEM -sfn +- tapOn: "Subscribe to this list" - tapOn: "Mute accounts" - tapOn: "Mute list" - tapOn: "Unmute" -- tapOn: - label: "Dropdown" - point: "71%,9%" - +- tapOn: "Subscribe to this list" - tapOn: "Block accounts" - tapOn: "Block list" - tapOn: "Unblock" - # the rest of the behaviors are tested in curate-lists.yml + # the rest of the behaviors are tested in curate-lists.yml diff --git a/__e2e__/flows/onboarding-avatar-creator.yml b/__e2e__/flows/onboarding-avatar-creator.yml index 3a20053ba..b8f65b9c3 100644 --- a/__e2e__/flows/onboarding-avatar-creator.yml +++ b/__e2e__/flows/onboarding-avatar-creator.yml @@ -3,11 +3,15 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: "?users" + SERVER_PATH: "?users" - runFlow: file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" + - tapOn: id: "e2eStartOnboarding" - tapOn: "Open avatar creator" @@ -21,12 +25,12 @@ appId: xyz.blueskyweb.app - tapOn: "Select the atom emoji as your avatar" - tapOn: "Done" - waitForAnimationToEnd -- tapOn: "Continue to next step" +- tapOn: + id: "onboardingContinue" - assertVisible: "What are your interests?" - tapOn: - label: "Tap on continue" - point: "50%,92%" + id: "onboardingContinue" - assertVisible: "You're ready to go!" - tapOn: - label: "Tap on Lets go" - point: "50%,92%" \ No newline at end of file + id: "onboardingFinish" +- assertVisible: "Following" diff --git a/__e2e__/flows/onboarding.yml b/__e2e__/flows/onboarding.yml index c8d9d5723..81aeef6cc 100644 --- a/__e2e__/flows/onboarding.yml +++ b/__e2e__/flows/onboarding.yml @@ -3,11 +3,15 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: "?users" + SERVER_PATH: "?users" - runFlow: file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" + - tapOn: id: "e2eStartOnboarding" - tapOn: "Select an avatar" @@ -18,12 +22,12 @@ appId: xyz.blueskyweb.app - waitForAnimationToEnd - tapOn: "Done" - waitForAnimationToEnd -- tapOn: "Continue to next step" +- tapOn: + id: "onboardingContinue" - assertVisible: "What are your interests?" - tapOn: - label: "Tap on continue" - point: "50%,92%" + id: "onboardingContinue" - assertVisible: "You're ready to go!" - tapOn: - label: "Tap on Lets go" - point: "50%,92%" \ No newline at end of file + id: "onboardingFinish" +- assertVisible: "Following" diff --git a/__e2e__/flows/post-report-flow.yml b/__e2e__/flows/post-report-flow.yml index 3925bef89..a509fcc02 100644 --- a/__e2e__/flows/post-report-flow.yml +++ b/__e2e__/flows/post-report-flow.yml @@ -3,11 +3,14 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: "?users&follows&posts" + SERVER_PATH: "?users&follows&posts" - runFlow: file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" - tapOn: id: "postDropdownBtn" diff --git a/__e2e__/flows/profile-screen.yml b/__e2e__/flows/profile-screen.yml index 858448b44..a20302dac 100644 --- a/__e2e__/flows/profile-screen.yml +++ b/__e2e__/flows/profile-screen.yml @@ -3,7 +3,7 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: "?users&posts&feeds" + SERVER_PATH: "?users&posts&feeds" - runFlow: file: ../setupApp.yml - tapOn: @@ -12,10 +12,10 @@ appId: xyz.blueskyweb.app # Navigate to another user profile - extendedWaitUntil: visible: - id: "bottomBarSearchBtn" + id: "bottomBarSearchBtn" - tapOn: id: "bottomBarSearchBtn" -- tapOn: "Search for posts, users, or feeds" +- tapOn: "Search for posts, users[,]? or feeds" - inputText: "b" - tapOn: id: "searchAutoCompleteResult-bob.test" @@ -38,4 +38,4 @@ appId: xyz.blueskyweb.app - tapOn: id: "profileHeaderDropdownBtn" - tapOn: "Unmute Account" -- assertNotVisible: "Account Muted" \ No newline at end of file +- assertNotVisible: "Account Muted" diff --git a/__e2e__/flows/search-screen.yml b/__e2e__/flows/search-screen.yml index 78ebbee36..13a1b06fa 100644 --- a/__e2e__/flows/search-screen.yml +++ b/__e2e__/flows/search-screen.yml @@ -3,20 +3,22 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: "?users" + SERVER_PATH: "?users" - runFlow: file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" # Navigate to another user profile via autocomplete +- extendedWaitUntil: + visible: + id: "bottomBarSearchBtn" - tapOn: id: "bottomBarSearchBtn" -- assertVisible: "Search for posts, users, or feeds" -- tapOn: "Search for posts, users, or feeds" +- assertVisible: "Search for posts, users[,]? or feeds" +- tapOn: "Search for posts, users[,]? or feeds" - inputText: "b" - tapOn: id: "searchAutoCompleteResult-bob.test" - assertVisible: id: "profileView" - diff --git a/__e2e__/flows/shared-prefs.yml b/__e2e__/flows/shared-prefs.yml index 9cc7707be..8b773a766 100644 --- a/__e2e__/flows/shared-prefs.yml +++ b/__e2e__/flows/shared-prefs.yml @@ -8,8 +8,12 @@ appId: xyz.blueskyweb.app file: ../setupApp.yml - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" + - assertVisible: - id: "storybookBtn" + id: "storybookBtn" - tapOn: id: "storybookBtn" - tapOn: diff --git a/__e2e__/flows/thread-muting.yml b/__e2e__/flows/thread-muting.yml index e588805c2..2724833fe 100644 --- a/__e2e__/flows/thread-muting.yml +++ b/__e2e__/flows/thread-muting.yml @@ -3,13 +3,16 @@ appId: xyz.blueskyweb.app - runScript: file: ../setupServer.js env: - SERVER_PATH: "?users&follows" + SERVER_PATH: "?users&follows" - runFlow: file: ../setupApp.yml # Login, create a thread, and log out - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" - assertVisible: id: "composeFAB" - tapOn: @@ -19,8 +22,13 @@ appId: xyz.blueskyweb.app id: "composerPublishBtn" # Login, reply to the thread, and log out +- tapOn: + id: "e2eSignOut" - tapOn: id: "e2eSignInBob" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" - tapOn: id: "replyBtn" - inputText: "Reply 1" @@ -28,8 +36,13 @@ appId: xyz.blueskyweb.app id: "composerPublishBtn" # Login, confirm notification exists, mute thread, and log out +- tapOn: + id: "e2eSignOut" - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" - tapOn: id: "bottomBarNotificationsBtn" - assertVisible: @@ -39,12 +52,17 @@ appId: xyz.blueskyweb.app - tapOn: id: "postDropdownBtn" childOf: - id: "postThreadItem-by-bob.test" + id: "postThreadItem-by-bob.test" - tapOn: "Mute thread" # Login, reply to the thread twice, and log out +- tapOn: + id: "e2eSignOut" - tapOn: id: "e2eSignInBob" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" - tapOn: id: "bottomBarProfileBtn" - tapOn: @@ -60,24 +78,35 @@ appId: xyz.blueskyweb.app - tapOn: id: "composerPublishBtn" - -# Login, confirm notifications dont exist, unmute the thread, confirm notifications exist +# Login, confirm notifications dont exist, unmute the thread, ~~confirm notifications exist~~ +# Mute thread behaviour no longer change old notifications after muting/unmuting a thread -sfn +- tapOn: + id: "e2eSignOut" - tapOn: id: "e2eSignInAlice" +- extendedWaitUntil: + visible: + id: "viewHeaderHomeFeedPrefsBtn" - tapOn: id: "bottomBarNotificationsBtn" -- assertNotVisible: +- assertVisible: ".*Reply 1.*" +- assertNotVisible: ".*Reply 2.*" +- assertNotVisible: ".*Reply 3.*" +- assertVisible: id: "feedItem-by-bob.test" - tapOn: - id: "bottomBarHomeBtn" + id: "feedItem-by-bob.test" - tapOn: id: "postDropdownBtn" + childOf: + id: "postThreadItem-by-bob.test" - tapOn: "Unmute thread" - tapOn: id: "bottomBarNotificationsBtn" - swipe: from: - id: "notifsFeed" + id: "notifsFeed" direction: DOWN -- assertVisible: - id: "feedItem-by-bob.test" +- assertVisible: ".*Reply 1.*" +- assertNotVisible: ".*Reply 2.*" +- assertNotVisible: ".*Reply 3.*" diff --git a/__e2e__/mock-server.ts b/__e2e__/mock-server.ts index d4be040df..1f7884844 100644 --- a/__e2e__/mock-server.ts +++ b/__e2e__/mock-server.ts @@ -181,10 +181,10 @@ async function main() { 'warn-profile', 'warn-posts', 'muted-account', - 'muted-by-list-account', + 'muted-by-list-acc', 'blocking-account', 'blockedby-account', - 'mutual-block-account', + 'mutual-block-acc', ]) { await server.mocker.createUser(user) await server.mocker.follow('alice', user) @@ -411,16 +411,16 @@ async function main() { await server.mocker.addToMuteList( 'alice', list, - server.mocker.users['muted-by-list-account'].did, + server.mocker.users['muted-by-list-acc'].did, ) - await server.mocker.createPost('muted-by-list-account', 'muted post') + await server.mocker.createPost('muted-by-list-acc', 'muted post') await server.mocker.createQuotePost( - 'muted-by-list-account', + 'muted-by-list-acc', 'account quote post', anchorPost, ) await server.mocker.createReply( - 'muted-by-list-account', + 'muted-by-list-acc', 'account reply', anchorPost, ) @@ -470,16 +470,16 @@ async function main() { ) await server.mocker.createPost( - 'mutual-block-account', + 'mutual-block-acc', 'mutual-block post', ) await server.mocker.createQuotePost( - 'mutual-block-account', + 'mutual-block-acc', 'mutual-block quote post', anchorPost, ) await server.mocker.createReply( - 'mutual-block-account', + 'mutual-block-acc', 'mutual-block reply', anchorPost, ) @@ -488,15 +488,15 @@ async function main() { repo: server.mocker.users.alice.did, }, { - subject: server.mocker.users['mutual-block-account'].did, + subject: server.mocker.users['mutual-block-acc'].did, createdAt: new Date().toISOString(), }, ) await server.mocker.users[ - 'mutual-block-account' + 'mutual-block-acc' ].agent.app.bsky.graph.block.create( { - repo: server.mocker.users['mutual-block-account'].did, + repo: server.mocker.users['mutual-block-acc'].did, }, { subject: server.mocker.users.alice.did, diff --git a/__e2e__/setupServer.js b/__e2e__/setupServer.js index dedf4ffa9..5038197b0 100644 --- a/__e2e__/setupServer.js +++ b/__e2e__/setupServer.js @@ -1,8 +1,8 @@ -// eslint-disable-next-line +// eslint-disable-next-line no-undef var res = http.post('http://localhost:1986/' + SERVER_PATH, { headers: {'Content-Type': 'text/plain'}, body: '', }) -// eslint-disable-next-line +// eslint-disable-next-line no-undef output.result = json(res.body).appviewDid diff --git a/src/lib/media/picker.e2e.tsx b/src/lib/media/picker.e2e.tsx index a2a9357ec..92e1495e5 100644 --- a/src/lib/media/picker.e2e.tsx +++ b/src/lib/media/picker.e2e.tsx @@ -7,6 +7,7 @@ import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool' import {compressIfNeeded} from './manip' import {type PickerImage} from './picker.shared' +import {ImagePickerResult} from 'expo-image-picker' async function getFile() { const imagesDir = documentDirectory! @@ -38,6 +39,22 @@ export async function openPicker(): Promise { return [await getFile()] } +export async function openUnifiedPicker(): Promise { + const file = await getFile() + + return { + assets: [ + { + type: 'image', + uri: file.path, + mimeType: file.mime, + ...file, + }, + ], + canceled: false, + } +} + export async function openCamera(): Promise { return await getFile() } diff --git a/src/lib/media/picker.shared.ts b/src/lib/media/picker.shared.ts index 8ec1154c8..6df712e9a 100644 --- a/src/lib/media/picker.shared.ts +++ b/src/lib/media/picker.shared.ts @@ -1,11 +1,14 @@ import { type ImagePickerOptions, launchImageLibraryAsync, + UIImagePickerPreferredAssetRepresentationMode, } from 'expo-image-picker' import {t} from '@lingui/macro' +import {isIOS, isWeb} from '#/platform/detection' import {type ImageMeta} from '#/state/gallery' import * as Toast from '#/view/com/util/Toast' +import {VIDEO_MAX_DURATION_MS} from '../constants' import {getDataUriSize} from './util' export type PickerImage = ImageMeta & { @@ -36,3 +39,22 @@ export async function openPicker(opts?: ImagePickerOptions) { size: getDataUriSize(image.uri), })) } + +export async function openUnifiedPicker({ + selectionCountRemaining, +}: { + selectionCountRemaining: number +}) { + return await launchImageLibraryAsync({ + exif: false, + mediaTypes: ['images', 'videos'], + quality: 1, + allowsMultipleSelection: true, + legacy: true, + base64: isWeb, + selectionLimit: isIOS ? selectionCountRemaining : undefined, + preferredAssetRepresentationMode: + UIImagePickerPreferredAssetRepresentationMode.Current, + videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000, + }) +} diff --git a/src/lib/media/picker.tsx b/src/lib/media/picker.tsx index 6095730d5..2526da3c8 100644 --- a/src/lib/media/picker.tsx +++ b/src/lib/media/picker.tsx @@ -1,7 +1,11 @@ import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool' import {type ImagePickerOptions, launchCameraAsync} from 'expo-image-picker' -export {openPicker, type PickerImage as RNImage} from './picker.shared' +export { + openPicker, + openUnifiedPicker, + type PickerImage as RNImage, +} from './picker.shared' export async function openCamera(customOpts: ImagePickerOptions) { const opts: ImagePickerOptions = { diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx index c1e4e4ab7..233600583 100644 --- a/src/lib/media/picker.web.tsx +++ b/src/lib/media/picker.web.tsx @@ -3,7 +3,7 @@ import {type OpenCropperOptions} from 'expo-image-crop-tool' import {type PickerImage} from './picker.shared' import {type CameraOpts} from './types' -export {openPicker} from './picker.shared' +export {openPicker, openUnifiedPicker} from './picker.shared' export async function openCamera(_opts: CameraOpts): Promise { throw new Error('openCamera is not supported on web') diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index f8040f3a5..c4b723ce1 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -580,6 +580,7 @@ function LegacyFinalStep({