v1.118.0 E2E tests (#9994)
This commit is contained in:
@@ -39,7 +39,6 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "editListNameInput"
|
id: "editListNameInput"
|
||||||
- eraseText
|
- eraseText
|
||||||
- inputText: "Bad Ppl"
|
- inputText: "Bad Ppl"
|
||||||
- hideKeyboard
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "editListDescriptionInput"
|
id: "editListDescriptionInput"
|
||||||
- eraseText
|
- eraseText
|
||||||
|
|||||||
@@ -35,9 +35,12 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "menuItemButton-Feeds"
|
id: "menuItemButton-Feeds"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "editFeedsBtn"
|
id: "editFeedsBtn"
|
||||||
- tapOn:
|
- swipe:
|
||||||
label: "Tap on down arrow"
|
label: "Drag feed down"
|
||||||
id: "feed-timeline-moveDown"
|
from:
|
||||||
|
id: "feed-drag-handle"
|
||||||
|
direction: "DOWN"
|
||||||
|
duration: 1000
|
||||||
- tapOn:
|
- tapOn:
|
||||||
label: "Save button"
|
label: "Save button"
|
||||||
id: "saveChangesBtn"
|
id: "saveChangesBtn"
|
||||||
@@ -55,9 +58,12 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "menuItemButton-Feeds"
|
id: "menuItemButton-Feeds"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "editFeedsBtn"
|
id: "editFeedsBtn"
|
||||||
- tapOn:
|
- swipe:
|
||||||
label: "Tap on down arrow"
|
label: "Drag feed down"
|
||||||
id: "feed-feed-moveDown"
|
from:
|
||||||
|
id: "feed-drag-handle"
|
||||||
|
direction: "DOWN"
|
||||||
|
duration: 1000
|
||||||
- tapOn:
|
- tapOn:
|
||||||
label: "Save button"
|
label: "Save button"
|
||||||
id: "saveChangesBtn"
|
id: "saveChangesBtn"
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ appId: xyz.blueskyweb.app
|
|||||||
- tapOn:
|
- tapOn:
|
||||||
id: "customServerTextInput"
|
id: "customServerTextInput"
|
||||||
- inputText: "http://localhost:3000"
|
- inputText: "http://localhost:3000"
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
- hideKeyboard
|
||||||
- tapOn: "Done"
|
- tapOn: "Done"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "loginUsernameInput"
|
id: "loginUsernameInput"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ appId: xyz.blueskyweb.app
|
|||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:details"
|
id: "report:details"
|
||||||
- inputText: "This is a test report"
|
- inputText: "This is a test report"
|
||||||
|
- hideKeyboard
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- assertNotVisible:
|
||||||
|
|||||||
+23
-9
@@ -3,15 +3,29 @@ appId: xyz.blueskyweb.app
|
|||||||
- launchApp:
|
- launchApp:
|
||||||
appId: "xyz.blueskyweb.app"
|
appId: "xyz.blueskyweb.app"
|
||||||
clearState: true
|
clearState: true
|
||||||
- waitForAnimationToEnd
|
arguments:
|
||||||
- tapOn: "http://localhost:8081"
|
"-EXDevMenuIsOnboardingFinished": true
|
||||||
- waitForAnimationToEnd
|
- runFlow:
|
||||||
- extendedWaitUntil:
|
when:
|
||||||
visible: "Continue"
|
platform: iOS
|
||||||
- swipe:
|
commands:
|
||||||
from: "Bluesky"
|
- openLink: "exp+bluesky://expo-development-client/?url=http%3A%2F%2Flocalhost%3A8081"
|
||||||
direction: DOWN
|
- runFlow:
|
||||||
duration: 100
|
when:
|
||||||
|
visible: 'Open in "Bluesky"'
|
||||||
|
commands:
|
||||||
|
- tapOn: Open
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
- tapOn: 'http://localhost:8081'
|
||||||
|
- runFlow:
|
||||||
|
label: "Dismiss Expo dev menu"
|
||||||
|
when:
|
||||||
|
visible: "Continue"
|
||||||
|
commands:
|
||||||
|
- back
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: e2eProxyHeaderInput
|
id: e2eProxyHeaderInput
|
||||||
- inputText: ${output.result}
|
- inputText: ${output.result}
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ values.
|
|||||||
2. You can write Maestro tests in `/.maestro/flows/` directory by creating a new `.yml` file or by modifying an existing one.
|
2. You can write Maestro tests in `/.maestro/flows/` directory by creating a new `.yml` file or by modifying an existing one.
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
### Running on Android
|
||||||
|
|
||||||
|
You will need to allow your device access to the port that the mock server is running on.
|
||||||
|
|
||||||
|
```
|
||||||
|
adb reverse tcp:3000 tcp:3000
|
||||||
|
```
|
||||||
|
|
||||||
### Running Maestro tests
|
### Running Maestro tests
|
||||||
|
|
||||||
- In one tab, run `yarn e2e:mock-server`
|
- In one tab, run `yarn e2e:mock-server`
|
||||||
|
|||||||
@@ -690,6 +690,7 @@ function HomeTabNavigator() {
|
|||||||
headerShown: true,
|
headerShown: true,
|
||||||
headerTransparent: true,
|
headerTransparent: true,
|
||||||
headerTitle: '',
|
headerTitle: '',
|
||||||
|
headerBackVisible: false,
|
||||||
scrollEdgeEffects: {
|
scrollEdgeEffects: {
|
||||||
top: 'soft',
|
top: 'soft',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ function SortableItem<T>({
|
|||||||
const dragHandle = (
|
const dragHandle = (
|
||||||
<GestureDetector gesture={gesture}>
|
<GestureDetector gesture={gesture}>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
|
testID="feed-drag-handle"
|
||||||
style={[
|
style={[
|
||||||
a.justify_center,
|
a.justify_center,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
|
|||||||
@@ -30,11 +30,15 @@ export function GrowableBanner({
|
|||||||
children,
|
children,
|
||||||
onPress,
|
onPress,
|
||||||
bannerRef,
|
bannerRef,
|
||||||
|
testID,
|
||||||
|
label,
|
||||||
}: {
|
}: {
|
||||||
backButton?: React.ReactNode
|
backButton?: React.ReactNode
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onPress?: () => void
|
onPress?: () => void
|
||||||
bannerRef?: AnimatedRef<Animated.View>
|
bannerRef?: AnimatedRef<Animated.View>
|
||||||
|
testID?: string
|
||||||
|
label?: string
|
||||||
}) {
|
}) {
|
||||||
const pagerContext = usePagerHeaderContext()
|
const pagerContext = usePagerHeaderContext()
|
||||||
|
|
||||||
@@ -42,8 +46,11 @@ export function GrowableBanner({
|
|||||||
if (!pagerContext || !IS_IOS) {
|
if (!pagerContext || !IS_IOS) {
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
testID={testID}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
accessibilityRole="image"
|
accessibilityRole="image"
|
||||||
|
accessibilityLabel={label}
|
||||||
|
accessibilityHint=""
|
||||||
style={[a.w_full, a.h_full]}>
|
style={[a.w_full, a.h_full]}>
|
||||||
<Animated.View ref={bannerRef} style={[a.w_full, a.h_full]}>
|
<Animated.View ref={bannerRef} style={[a.w_full, a.h_full]}>
|
||||||
{children}
|
{children}
|
||||||
@@ -60,7 +67,9 @@ export function GrowableBanner({
|
|||||||
scrollY={scrollY}
|
scrollY={scrollY}
|
||||||
backButton={backButton}
|
backButton={backButton}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
bannerRef={bannerRef}>
|
bannerRef={bannerRef}
|
||||||
|
testID={testID}
|
||||||
|
label={label}>
|
||||||
{children}
|
{children}
|
||||||
</GrowableBannerInner>
|
</GrowableBannerInner>
|
||||||
)
|
)
|
||||||
@@ -72,12 +81,16 @@ function GrowableBannerInner({
|
|||||||
children,
|
children,
|
||||||
onPress,
|
onPress,
|
||||||
bannerRef,
|
bannerRef,
|
||||||
|
testID,
|
||||||
|
label,
|
||||||
}: {
|
}: {
|
||||||
scrollY: SharedValue<number>
|
scrollY: SharedValue<number>
|
||||||
backButton?: React.ReactNode
|
backButton?: React.ReactNode
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onPress?: () => void
|
onPress?: () => void
|
||||||
bannerRef?: AnimatedRef<Animated.View>
|
bannerRef?: AnimatedRef<Animated.View>
|
||||||
|
testID?: string
|
||||||
|
label?: string
|
||||||
}) {
|
}) {
|
||||||
const {top: topInset} = useSafeAreaInsets()
|
const {top: topInset} = useSafeAreaInsets()
|
||||||
const isFetching = useIsProfileFetching()
|
const isFetching = useIsProfileFetching()
|
||||||
@@ -142,8 +155,11 @@ function GrowableBannerInner({
|
|||||||
animatedStyle,
|
animatedStyle,
|
||||||
]}>
|
]}>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
testID={testID}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
accessibilityRole="image"
|
accessibilityRole="image"
|
||||||
|
accessibilityLabel={label}
|
||||||
|
accessibilityHint=""
|
||||||
style={[a.w_full, a.h_full]}>
|
style={[a.w_full, a.h_full]}>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
ref={bannerRef}
|
ref={bannerRef}
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import Animated, {
|
|||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api'
|
import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api'
|
||||||
import {utils} from '@bsky.app/alf'
|
import {utils} from '@bsky.app/alf'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
import {BACK_HITSLOP} from '#/lib/constants'
|
import {BACK_HITSLOP} from '#/lib/constants'
|
||||||
@@ -56,7 +55,7 @@ let ProfileHeaderShell = ({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const {openLightbox} = useLightboxControls()
|
const {openLightbox} = useLightboxControls()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const {top: topInset} = useSafeAreaInsets()
|
const {top: topInset} = useSafeAreaInsets()
|
||||||
@@ -168,6 +167,12 @@ let ProfileHeaderShell = ({
|
|||||||
style={[a.relative, {height: 150}]}>
|
style={[a.relative, {height: 150}]}>
|
||||||
<StatusBarShadow />
|
<StatusBarShadow />
|
||||||
<GrowableBanner
|
<GrowableBanner
|
||||||
|
testID={profile.banner ? 'userBannerImage' : 'userBannerFallback'}
|
||||||
|
label={
|
||||||
|
profile.banner
|
||||||
|
? l`View profile banner`
|
||||||
|
: l`Profile banner placeholder`
|
||||||
|
}
|
||||||
onPress={isPlaceholderProfile ? undefined : onPressBanner}
|
onPress={isPlaceholderProfile ? undefined : onPressBanner}
|
||||||
bannerRef={bannerRef}
|
bannerRef={bannerRef}
|
||||||
backButton={
|
backButton={
|
||||||
@@ -176,7 +181,7 @@ let ProfileHeaderShell = ({
|
|||||||
testID="profileHeaderBackBtn"
|
testID="profileHeaderBackBtn"
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
hitSlop={BACK_HITSLOP}
|
hitSlop={BACK_HITSLOP}
|
||||||
label={_(msg`Back`)}
|
label={l`Back`}
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
a.absolute,
|
||||||
a.pointer,
|
a.pointer,
|
||||||
@@ -259,7 +264,7 @@ let ProfileHeaderShell = ({
|
|||||||
testID="profileHeaderAviButton"
|
testID="profileHeaderAviButton"
|
||||||
onPress={onPressAvi}
|
onPress={onPressAvi}
|
||||||
accessibilityRole="image"
|
accessibilityRole="image"
|
||||||
accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)}
|
accessibilityLabel={l`View ${profile.handle}'s avatar`}
|
||||||
accessibilityHint="">
|
accessibilityHint="">
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export function MoreOptionsMenu({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Menu.Trigger>
|
</Menu.Trigger>
|
||||||
<Menu.Outer>
|
<Menu.Outer showCancel>
|
||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
label={IS_WEB ? _(msg`Copy link to list`) : _(msg`Share via...`)}
|
label={IS_WEB ? _(msg`Copy link to list`) : _(msg`Share via...`)}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
|
|||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const actors = accounts.map(acc => acc.did)
|
const actors = accounts.map(acc => acc.did)
|
||||||
|
if (actors.length === 0) return
|
||||||
void queryClient.prefetchQuery({
|
void queryClient.prefetchQuery({
|
||||||
queryKey: profilesQueryKey(actors),
|
queryKey: profilesQueryKey(actors),
|
||||||
staleTime: STALE.MINUTES.FIVE,
|
staleTime: STALE.MINUTES.FIVE,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export function TestCtrls() {
|
|||||||
const onboardingDispatch = useOnboardingDispatch()
|
const onboardingDispatch = useOnboardingDispatch()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
const onPressSignInAlice = async () => {
|
const onPressSignInAlice = async () => {
|
||||||
|
console.info('[E2E] Signing in as Alice')
|
||||||
await login(
|
await login(
|
||||||
{
|
{
|
||||||
service: 'http://localhost:3000',
|
service: 'http://localhost:3000',
|
||||||
@@ -36,6 +37,7 @@ export function TestCtrls() {
|
|||||||
setShowLoggedOut(false)
|
setShowLoggedOut(false)
|
||||||
}
|
}
|
||||||
const onPressSignInBob = async () => {
|
const onPressSignInBob = async () => {
|
||||||
|
console.info('[E2E] Signing in as Bob')
|
||||||
await login(
|
await login(
|
||||||
{
|
{
|
||||||
service: 'http://localhost:3000',
|
service: 'http://localhost:3000',
|
||||||
@@ -54,6 +56,9 @@ export function TestCtrls() {
|
|||||||
accessibilityHint="Enter proxy header"
|
accessibilityHint="Enter proxy header"
|
||||||
testID="e2eProxyHeaderInput"
|
testID="e2eProxyHeaderInput"
|
||||||
onChangeText={val => setProxyHeader(val as any)}
|
onChangeText={val => setProxyHeader(val as any)}
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect={false}
|
||||||
|
autoCapitalize="none"
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
const header = `${proxyHeader}#bsky_appview`
|
const header = `${proxyHeader}#bsky_appview`
|
||||||
BLUESKY_PROXY_HEADER.set(header)
|
BLUESKY_PROXY_HEADER.set(header)
|
||||||
@@ -135,15 +140,6 @@ export function TestCtrls() {
|
|||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
style={BTN}
|
style={BTN}
|
||||||
/>
|
/>
|
||||||
{/* TODO remove this entire control when experiment is over */}
|
|
||||||
<Pressable
|
|
||||||
testID="e2eStartLongboarding"
|
|
||||||
onPress={() => {
|
|
||||||
onboardingDispatch({type: 'start'})
|
|
||||||
}}
|
|
||||||
accessibilityRole="button"
|
|
||||||
style={BTN}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,7 +210,6 @@ export function UserBanner({
|
|||||||
) : banner &&
|
) : banner &&
|
||||||
!((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? (
|
!((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? (
|
||||||
<Image
|
<Image
|
||||||
testID="userBannerImage"
|
|
||||||
style={[styles.bannerImage, t.atoms.bg_contrast_25]}
|
style={[styles.bannerImage, t.atoms.bg_contrast_25]}
|
||||||
contentFit="cover"
|
contentFit="cover"
|
||||||
source={{uri: banner}}
|
source={{uri: banner}}
|
||||||
@@ -220,7 +219,6 @@ export function UserBanner({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View
|
<View
|
||||||
testID="userBannerFallback"
|
|
||||||
style={[
|
style={[
|
||||||
styles.bannerImage,
|
styles.bannerImage,
|
||||||
type === 'labeler' ? styles.labelerBanner : t.atoms.bg_contrast_25,
|
type === 'labeler' ? styles.labelerBanner : t.atoms.bg_contrast_25,
|
||||||
|
|||||||
Reference in New Issue
Block a user