Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

v1.118.0 E2E tests (#9994)

authored by samuel.fm and committed by

GitHub 30c9ea57 afbade6f

+85 -34
-1
__e2e__/flows/curate-lists.yml
··· 39 id: "editListNameInput" 40 - eraseText 41 - inputText: "Bad Ppl" 42 - - hideKeyboard 43 - tapOn: 44 id: "editListDescriptionInput" 45 - eraseText
··· 39 id: "editListNameInput" 40 - eraseText 41 - inputText: "Bad Ppl" 42 - tapOn: 43 id: "editListDescriptionInput" 44 - eraseText
+12 -6
__e2e__/flows/feed-reorder.yml
··· 35 id: "menuItemButton-Feeds" 36 - tapOn: 37 id: "editFeedsBtn" 38 - - tapOn: 39 - label: "Tap on down arrow" 40 - id: "feed-timeline-moveDown" 41 - tapOn: 42 label: "Save button" 43 id: "saveChangesBtn" ··· 55 id: "menuItemButton-Feeds" 56 - tapOn: 57 id: "editFeedsBtn" 58 - - tapOn: 59 - label: "Tap on down arrow" 60 - id: "feed-feed-moveDown" 61 - tapOn: 62 label: "Save button" 63 id: "saveChangesBtn"
··· 35 id: "menuItemButton-Feeds" 36 - tapOn: 37 id: "editFeedsBtn" 38 + - swipe: 39 + label: "Drag feed down" 40 + from: 41 + id: "feed-drag-handle" 42 + direction: "DOWN" 43 + duration: 1000 44 - tapOn: 45 label: "Save button" 46 id: "saveChangesBtn" ··· 58 id: "menuItemButton-Feeds" 59 - tapOn: 60 id: "editFeedsBtn" 61 + - swipe: 62 + label: "Drag feed down" 63 + from: 64 + id: "feed-drag-handle" 65 + direction: "DOWN" 66 + duration: 1000 67 - tapOn: 68 label: "Save button" 69 id: "saveChangesBtn"
+5
__e2e__/flows/login.yml
··· 15 - tapOn: 16 id: "customServerTextInput" 17 - inputText: "http://localhost:3000" 18 - tapOn: "Done" 19 - tapOn: 20 id: "loginUsernameInput"
··· 15 - tapOn: 16 id: "customServerTextInput" 17 - inputText: "http://localhost:3000" 18 + - runFlow: 19 + when: 20 + platform: Android 21 + commands: 22 + - hideKeyboard 23 - tapOn: "Done" 24 - tapOn: 25 id: "loginUsernameInput"
+1
__e2e__/flows/report-dialog/post.reason-other.yml
··· 26 - tapOn: 27 id: "report:details" 28 - inputText: "This is a test report" 29 - tapOn: 30 id: "report:submit" 31 - assertNotVisible:
··· 26 - tapOn: 27 id: "report:details" 28 - inputText: "This is a test report" 29 + - hideKeyboard 30 - tapOn: 31 id: "report:submit" 32 - assertNotVisible:
+23 -9
__e2e__/setupApp.yml
··· 3 - launchApp: 4 appId: "xyz.blueskyweb.app" 5 clearState: true 6 - - waitForAnimationToEnd 7 - - tapOn: "http://localhost:8081" 8 - - waitForAnimationToEnd 9 - - extendedWaitUntil: 10 - visible: "Continue" 11 - - swipe: 12 - from: "Bluesky" 13 - direction: DOWN 14 - duration: 100 15 - tapOn: 16 id: e2eProxyHeaderInput 17 - inputText: ${output.result}
··· 3 - launchApp: 4 appId: "xyz.blueskyweb.app" 5 clearState: true 6 + arguments: 7 + "-EXDevMenuIsOnboardingFinished": true 8 + - runFlow: 9 + when: 10 + platform: iOS 11 + commands: 12 + - openLink: "exp+bluesky://expo-development-client/?url=http%3A%2F%2Flocalhost%3A8081" 13 + - runFlow: 14 + when: 15 + visible: 'Open in "Bluesky"' 16 + commands: 17 + - tapOn: Open 18 + - runFlow: 19 + when: 20 + platform: Android 21 + commands: 22 + - tapOn: 'http://localhost:8081' 23 + - runFlow: 24 + label: "Dismiss Expo dev menu" 25 + when: 26 + visible: "Continue" 27 + commands: 28 + - back 29 - tapOn: 30 id: e2eProxyHeaderInput 31 - inputText: ${output.result}
+8
docs/testing.md
··· 9 2. You can write Maestro tests in `/.maestro/flows/` directory by creating a new `.yml` file or by modifying an existing one. 10 3. You can also use [Maestro Studio](https://maestro.mobile.dev/getting-started/maestro-studio) which automatically generates commands by recording your actions on the app. Therefore, you can create realistic tests without having to manually write any code. Use the `maestro studio` command to start recording your actions. 11 12 ### Running Maestro tests 13 14 - In one tab, run `yarn e2e:mock-server`
··· 9 2. You can write Maestro tests in `/.maestro/flows/` directory by creating a new `.yml` file or by modifying an existing one. 10 3. You can also use [Maestro Studio](https://maestro.mobile.dev/getting-started/maestro-studio) which automatically generates commands by recording your actions on the app. Therefore, you can create realistic tests without having to manually write any code. Use the `maestro studio` command to start recording your actions. 11 12 + ### Running on Android 13 + 14 + You will need to allow your device access to the port that the mock server is running on. 15 + 16 + ``` 17 + adb reverse tcp:3000 tcp:3000 18 + ``` 19 + 20 ### Running Maestro tests 21 22 - In one tab, run `yarn e2e:mock-server`
+1
src/Navigation.tsx
··· 690 headerShown: true, 691 headerTransparent: true, 692 headerTitle: '', 693 scrollEdgeEffects: { 694 top: 'soft', 695 },
··· 690 headerShown: true, 691 headerTransparent: true, 692 headerTitle: '', 693 + headerBackVisible: false, 694 scrollEdgeEffects: { 695 top: 'soft', 696 },
+1
src/components/DraggableList/index.tsx
··· 454 const dragHandle = ( 455 <GestureDetector gesture={gesture}> 456 <Animated.View 457 style={[ 458 a.justify_center, 459 a.align_center,
··· 454 const dragHandle = ( 455 <GestureDetector gesture={gesture}> 456 <Animated.View 457 + testID="feed-drag-handle" 458 style={[ 459 a.justify_center, 460 a.align_center,
+17 -1
src/screens/Profile/Header/GrowableBanner.tsx
··· 30 children, 31 onPress, 32 bannerRef, 33 }: { 34 backButton?: React.ReactNode 35 children: React.ReactNode 36 onPress?: () => void 37 bannerRef?: AnimatedRef<Animated.View> 38 }) { 39 const pagerContext = usePagerHeaderContext() 40 ··· 42 if (!pagerContext || !IS_IOS) { 43 return ( 44 <Pressable 45 onPress={onPress} 46 accessibilityRole="image" 47 style={[a.w_full, a.h_full]}> 48 <Animated.View ref={bannerRef} style={[a.w_full, a.h_full]}> 49 {children} ··· 60 scrollY={scrollY} 61 backButton={backButton} 62 onPress={onPress} 63 - bannerRef={bannerRef}> 64 {children} 65 </GrowableBannerInner> 66 ) ··· 72 children, 73 onPress, 74 bannerRef, 75 }: { 76 scrollY: SharedValue<number> 77 backButton?: React.ReactNode 78 children: React.ReactNode 79 onPress?: () => void 80 bannerRef?: AnimatedRef<Animated.View> 81 }) { 82 const {top: topInset} = useSafeAreaInsets() 83 const isFetching = useIsProfileFetching() ··· 142 animatedStyle, 143 ]}> 144 <Pressable 145 onPress={onPress} 146 accessibilityRole="image" 147 style={[a.w_full, a.h_full]}> 148 <Animated.View 149 ref={bannerRef}
··· 30 children, 31 onPress, 32 bannerRef, 33 + testID, 34 + label, 35 }: { 36 backButton?: React.ReactNode 37 children: React.ReactNode 38 onPress?: () => void 39 bannerRef?: AnimatedRef<Animated.View> 40 + testID?: string 41 + label?: string 42 }) { 43 const pagerContext = usePagerHeaderContext() 44 ··· 46 if (!pagerContext || !IS_IOS) { 47 return ( 48 <Pressable 49 + testID={testID} 50 onPress={onPress} 51 accessibilityRole="image" 52 + accessibilityLabel={label} 53 + accessibilityHint="" 54 style={[a.w_full, a.h_full]}> 55 <Animated.View ref={bannerRef} style={[a.w_full, a.h_full]}> 56 {children} ··· 67 scrollY={scrollY} 68 backButton={backButton} 69 onPress={onPress} 70 + bannerRef={bannerRef} 71 + testID={testID} 72 + label={label}> 73 {children} 74 </GrowableBannerInner> 75 ) ··· 81 children, 82 onPress, 83 bannerRef, 84 + testID, 85 + label, 86 }: { 87 scrollY: SharedValue<number> 88 backButton?: React.ReactNode 89 children: React.ReactNode 90 onPress?: () => void 91 bannerRef?: AnimatedRef<Animated.View> 92 + testID?: string 93 + label?: string 94 }) { 95 const {top: topInset} = useSafeAreaInsets() 96 const isFetching = useIsProfileFetching() ··· 155 animatedStyle, 156 ]}> 157 <Pressable 158 + testID={testID} 159 onPress={onPress} 160 accessibilityRole="image" 161 + accessibilityLabel={label} 162 + accessibilityHint="" 163 style={[a.w_full, a.h_full]}> 164 <Animated.View 165 ref={bannerRef}
+10 -5
src/screens/Profile/Header/Shell.tsx
··· 10 import {useSafeAreaInsets} from 'react-native-safe-area-context' 11 import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' 12 import {utils} from '@bsky.app/alf' 13 - import {msg} from '@lingui/core/macro' 14 - import {useLingui} from '@lingui/react' 15 import {useNavigation} from '@react-navigation/native' 16 17 import {BACK_HITSLOP} from '#/lib/constants' ··· 56 const t = useTheme() 57 const ax = useAnalytics() 58 const {currentAccount} = useSession() 59 - const {_} = useLingui() 60 const {openLightbox} = useLightboxControls() 61 const navigation = useNavigation<NavigationProp>() 62 const {top: topInset} = useSafeAreaInsets() ··· 168 style={[a.relative, {height: 150}]}> 169 <StatusBarShadow /> 170 <GrowableBanner 171 onPress={isPlaceholderProfile ? undefined : onPressBanner} 172 bannerRef={bannerRef} 173 backButton={ ··· 176 testID="profileHeaderBackBtn" 177 onPress={onPressBack} 178 hitSlop={BACK_HITSLOP} 179 - label={_(msg`Back`)} 180 style={[ 181 a.absolute, 182 a.pointer, ··· 259 testID="profileHeaderAviButton" 260 onPress={onPressAvi} 261 accessibilityRole="image" 262 - accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)} 263 accessibilityHint=""> 264 <View 265 style={[
··· 10 import {useSafeAreaInsets} from 'react-native-safe-area-context' 11 import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' 12 import {utils} from '@bsky.app/alf' 13 + import {useLingui} from '@lingui/react/macro' 14 import {useNavigation} from '@react-navigation/native' 15 16 import {BACK_HITSLOP} from '#/lib/constants' ··· 55 const t = useTheme() 56 const ax = useAnalytics() 57 const {currentAccount} = useSession() 58 + const {t: l} = useLingui() 59 const {openLightbox} = useLightboxControls() 60 const navigation = useNavigation<NavigationProp>() 61 const {top: topInset} = useSafeAreaInsets() ··· 167 style={[a.relative, {height: 150}]}> 168 <StatusBarShadow /> 169 <GrowableBanner 170 + testID={profile.banner ? 'userBannerImage' : 'userBannerFallback'} 171 + label={ 172 + profile.banner 173 + ? l`View profile banner` 174 + : l`Profile banner placeholder` 175 + } 176 onPress={isPlaceholderProfile ? undefined : onPressBanner} 177 bannerRef={bannerRef} 178 backButton={ ··· 181 testID="profileHeaderBackBtn" 182 onPress={onPressBack} 183 hitSlop={BACK_HITSLOP} 184 + label={l`Back`} 185 style={[ 186 a.absolute, 187 a.pointer, ··· 264 testID="profileHeaderAviButton" 265 onPress={onPressAvi} 266 accessibilityRole="image" 267 + accessibilityLabel={l`View ${profile.handle}'s avatar`} 268 accessibilityHint=""> 269 <View 270 style={[
+1 -1
src/screens/ProfileList/components/MoreOptionsMenu.tsx
··· 154 </Button> 155 )} 156 </Menu.Trigger> 157 - <Menu.Outer> 158 <Menu.Group> 159 <Menu.Item 160 label={IS_WEB ? _(msg`Copy link to list`) : _(msg`Share via...`)}
··· 154 </Button> 155 )} 156 </Menu.Trigger> 157 + <Menu.Outer showCancel> 158 <Menu.Group> 159 <Menu.Item 160 label={IS_WEB ? _(msg`Copy link to list`) : _(msg`Share via...`)}
+1
src/view/com/auth/LoggedOut.tsx
··· 57 const agent = useAgent() 58 useEffect(() => { 59 const actors = accounts.map(acc => acc.did) 60 void queryClient.prefetchQuery({ 61 queryKey: profilesQueryKey(actors), 62 staleTime: STALE.MINUTES.FIVE,
··· 57 const agent = useAgent() 58 useEffect(() => { 59 const actors = accounts.map(acc => acc.did) 60 + if (actors.length === 0) return 61 void queryClient.prefetchQuery({ 62 queryKey: profilesQueryKey(actors), 63 staleTime: STALE.MINUTES.FIVE,
+5 -9
src/view/com/testing/TestCtrls.e2e.tsx
··· 25 const onboardingDispatch = useOnboardingDispatch() 26 const {setShowLoggedOut} = useLoggedOutViewControls() 27 const onPressSignInAlice = async () => { 28 await login( 29 { 30 service: 'http://localhost:3000', ··· 36 setShowLoggedOut(false) 37 } 38 const onPressSignInBob = async () => { 39 await login( 40 { 41 service: 'http://localhost:3000', ··· 54 accessibilityHint="Enter proxy header" 55 testID="e2eProxyHeaderInput" 56 onChangeText={val => setProxyHeader(val as any)} 57 onSubmitEditing={() => { 58 const header = `${proxyHeader}#bsky_appview` 59 BLUESKY_PROXY_HEADER.set(header) ··· 129 /> 130 <Pressable 131 testID="e2eStartOnboarding" 132 - onPress={() => { 133 - onboardingDispatch({type: 'start'}) 134 - }} 135 - accessibilityRole="button" 136 - style={BTN} 137 - /> 138 - {/* TODO remove this entire control when experiment is over */} 139 - <Pressable 140 - testID="e2eStartLongboarding" 141 onPress={() => { 142 onboardingDispatch({type: 'start'}) 143 }}
··· 25 const onboardingDispatch = useOnboardingDispatch() 26 const {setShowLoggedOut} = useLoggedOutViewControls() 27 const onPressSignInAlice = async () => { 28 + console.info('[E2E] Signing in as Alice') 29 await login( 30 { 31 service: 'http://localhost:3000', ··· 37 setShowLoggedOut(false) 38 } 39 const onPressSignInBob = async () => { 40 + console.info('[E2E] Signing in as Bob') 41 await login( 42 { 43 service: 'http://localhost:3000', ··· 56 accessibilityHint="Enter proxy header" 57 testID="e2eProxyHeaderInput" 58 onChangeText={val => setProxyHeader(val as any)} 59 + autoComplete="off" 60 + autoCorrect={false} 61 + autoCapitalize="none" 62 onSubmitEditing={() => { 63 const header = `${proxyHeader}#bsky_appview` 64 BLUESKY_PROXY_HEADER.set(header) ··· 134 /> 135 <Pressable 136 testID="e2eStartOnboarding" 137 onPress={() => { 138 onboardingDispatch({type: 'start'}) 139 }}
-2
src/view/com/util/UserBanner.tsx
··· 210 ) : banner && 211 !((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? ( 212 <Image 213 - testID="userBannerImage" 214 style={[styles.bannerImage, t.atoms.bg_contrast_25]} 215 contentFit="cover" 216 source={{uri: banner}} ··· 220 /> 221 ) : ( 222 <View 223 - testID="userBannerFallback" 224 style={[ 225 styles.bannerImage, 226 type === 'labeler' ? styles.labelerBanner : t.atoms.bg_contrast_25,
··· 210 ) : banner && 211 !((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? ( 212 <Image 213 style={[styles.bannerImage, t.atoms.bg_contrast_25]} 214 contentFit="cover" 215 source={{uri: banner}} ··· 219 /> 220 ) : ( 221 <View 222 style={[ 223 styles.bannerImage, 224 type === 'labeler' ? styles.labelerBanner : t.atoms.bg_contrast_25,