fix android e2e flow failures: matchers, timing, media seeding, gestures
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -153,6 +153,16 @@ if [[ "$platform" == "android" ]]; then
|
|||||||
phase "Configuring Android localhost routing"
|
phase "Configuring Android localhost routing"
|
||||||
adb -s "$device_id" reverse tcp:3000 tcp:3000
|
adb -s "$device_id" reverse tcp:3000 tcp:3000
|
||||||
adb -s "$device_id" reverse tcp:8081 tcp:8081
|
adb -s "$device_id" reverse tcp:8081 tcp:8081
|
||||||
|
|
||||||
|
# The e2e media picker mock reads a seeded photo from the app's external
|
||||||
|
# files directory (see src/lib/media/picker.e2e.tsx). The app must already
|
||||||
|
# be installed (the install step runs before this script), so the directory
|
||||||
|
# exists and is owned by the app; sdcardfs makes it world-readable so the
|
||||||
|
# mock can read the file without any runtime media permission.
|
||||||
|
phase "Seeding e2e media"
|
||||||
|
e2e_media_dir="/sdcard/Android/data/xyz.blueskyweb.app/files/e2e"
|
||||||
|
adb -s "$device_id" shell mkdir -p "$e2e_media_dir"
|
||||||
|
adb -s "$device_id" push assets/images/welcome-modal-bg.jpg "$e2e_media_dir/test.jpg"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
phase "Running Maestro flows"
|
phase "Running Maestro flows"
|
||||||
|
|||||||
@@ -35,12 +35,31 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "menuItemButton-Feeds"
|
id: "menuItemButton-Feeds"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "editFeedsBtn"
|
id: "editFeedsBtn"
|
||||||
- swipe:
|
# The id+direction swipe doesn't register the RNGH pan on Android, so we
|
||||||
label: "Drag feed down"
|
# use an explicit coordinate swipe that travels several row heights. iOS
|
||||||
from:
|
# keeps the id-based swipe (its screen aspect ratio differs).
|
||||||
id: "feed-drag-handle"
|
- runFlow:
|
||||||
direction: "DOWN"
|
when:
|
||||||
duration: 1000
|
platform: iOS
|
||||||
|
commands:
|
||||||
|
- swipe:
|
||||||
|
label: "Drag feed down"
|
||||||
|
from:
|
||||||
|
id: "feed-drag-handle"
|
||||||
|
direction: "DOWN"
|
||||||
|
duration: 1000
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
- swipe:
|
||||||
|
label: "Drag feed down"
|
||||||
|
start: "93%,31%"
|
||||||
|
end: "93%,45%"
|
||||||
|
duration: 1500
|
||||||
|
- assertVisible:
|
||||||
|
id: "saveChangesBtn"
|
||||||
|
enabled: true
|
||||||
- tapOn:
|
- tapOn:
|
||||||
label: "Save button"
|
label: "Save button"
|
||||||
id: "saveChangesBtn"
|
id: "saveChangesBtn"
|
||||||
@@ -58,12 +77,28 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "menuItemButton-Feeds"
|
id: "menuItemButton-Feeds"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "editFeedsBtn"
|
id: "editFeedsBtn"
|
||||||
- swipe:
|
- runFlow:
|
||||||
label: "Drag feed down"
|
when:
|
||||||
from:
|
platform: iOS
|
||||||
id: "feed-drag-handle"
|
commands:
|
||||||
direction: "DOWN"
|
- swipe:
|
||||||
duration: 1000
|
label: "Drag feed down"
|
||||||
|
from:
|
||||||
|
id: "feed-drag-handle"
|
||||||
|
direction: "DOWN"
|
||||||
|
duration: 1000
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
- swipe:
|
||||||
|
label: "Drag feed down"
|
||||||
|
start: "93%,31%"
|
||||||
|
end: "93%,45%"
|
||||||
|
duration: 1500
|
||||||
|
- assertVisible:
|
||||||
|
id: "saveChangesBtn"
|
||||||
|
enabled: true
|
||||||
- tapOn:
|
- tapOn:
|
||||||
label: "Save button"
|
label: "Save button"
|
||||||
id: "saveChangesBtn"
|
id: "saveChangesBtn"
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ appId: xyz.blueskyweb.app
|
|||||||
- tapOn:
|
- tapOn:
|
||||||
id: "e2eStartOnboarding"
|
id: "e2eStartOnboarding"
|
||||||
- tapOn: "Open avatar creator"
|
- tapOn: "Open avatar creator"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible: "Select an emoji"
|
||||||
|
timeout: 15000
|
||||||
- tapOn: "Select the zap emoji as your avatar"
|
- tapOn: "Select the zap emoji as your avatar"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
label: "Tap on yellow"
|
label: "Tap on yellow"
|
||||||
|
|||||||
@@ -16,13 +16,28 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "e2eStartOnboarding"
|
id: "e2eStartOnboarding"
|
||||||
- tapOn: "Select an avatar"
|
- tapOn: "Select an avatar"
|
||||||
- waitForAnimationToEnd
|
- waitForAnimationToEnd
|
||||||
- assertVisible: "Photos"
|
- runFlow:
|
||||||
- assertVisible: "Collections"
|
when:
|
||||||
- tapOn:
|
platform: iOS
|
||||||
point: "50%,22%"
|
commands:
|
||||||
- waitForAnimationToEnd
|
- assertVisible: "Photos"
|
||||||
- tapOn: "Done"
|
- assertVisible: "Collections"
|
||||||
- waitForAnimationToEnd
|
- tapOn:
|
||||||
|
point: "50%,22%"
|
||||||
|
- waitForAnimationToEnd
|
||||||
|
- tapOn: "Done"
|
||||||
|
- waitForAnimationToEnd
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
# The system photo picker opened here shows MediaStore photos, which
|
||||||
|
# the e2e run doesn't seed (media is seeded into app-scoped storage for
|
||||||
|
# the composer's mocked picker instead). With no photo to pick, dismiss
|
||||||
|
# the picker and continue - onContinue falls back to the generated
|
||||||
|
# placeholder avatar, and nothing later in the flow depends on the image.
|
||||||
|
- back
|
||||||
|
- waitForAnimationToEnd
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "onboardingContinue"
|
id: "onboardingContinue"
|
||||||
- assertVisible: "What are your interests?"
|
- assertVisible: "What are your interests?"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "editProfileSaveBtn"
|
id: "editProfileSaveBtn"
|
||||||
- assertNotVisible:
|
- assertNotVisible:
|
||||||
id: "editProfileModal"
|
id: "editProfileModal"
|
||||||
- assertVisible: "Alicia"
|
- assertVisible: ".*Alicia.*"
|
||||||
- assertVisible: "One cool hacker"
|
- assertVisible: "One cool hacker"
|
||||||
|
|
||||||
# Remove display name and description via the edit profile modal
|
# Remove display name and description via the edit profile modal
|
||||||
|
|||||||
@@ -22,5 +22,7 @@ appId: xyz.blueskyweb.app
|
|||||||
text: "Send report to Dev-env Moderation"
|
text: "Send report to Dev-env Moderation"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- extendedWaitUntil:
|
||||||
id: "report:dialog"
|
notVisible:
|
||||||
|
id: "report:dialog"
|
||||||
|
timeout: 20000
|
||||||
|
|||||||
@@ -22,5 +22,7 @@ appId: xyz.blueskyweb.app
|
|||||||
text: "Send report to Dev-env Moderation"
|
text: "Send report to Dev-env Moderation"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- extendedWaitUntil:
|
||||||
id: "report:dialog"
|
notVisible:
|
||||||
|
id: "report:dialog"
|
||||||
|
timeout: 20000
|
||||||
|
|||||||
@@ -39,5 +39,7 @@ appId: xyz.blueskyweb.app
|
|||||||
text: Your report will be sent to Dev-env Moderation.*
|
text: Your report will be sent to Dev-env Moderation.*
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- extendedWaitUntil:
|
||||||
id: "report:dialog"
|
notVisible:
|
||||||
|
id: "report:dialog"
|
||||||
|
timeout: 20000
|
||||||
|
|||||||
@@ -29,5 +29,7 @@ appId: xyz.blueskyweb.app
|
|||||||
- hideKeyboard
|
- hideKeyboard
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- extendedWaitUntil:
|
||||||
id: "report:dialog"
|
notVisible:
|
||||||
|
id: "report:dialog"
|
||||||
|
timeout: 20000
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "viewHeaderHomeFeedPrefsBtn"
|
id: "viewHeaderHomeFeedPrefsBtn"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "bottomBarNotificationsBtn"
|
id: "bottomBarNotificationsBtn"
|
||||||
- assertVisible:
|
- assertVisible: ".*Reply 1.*"
|
||||||
id: "feedItem-by-bob.test"
|
- tapOn: ".*Reply 1.*"
|
||||||
- tapOn:
|
|
||||||
id: "feedItem-by-bob.test"
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "postDropdownBtn"
|
id: "postDropdownBtn"
|
||||||
childOf:
|
childOf:
|
||||||
@@ -92,10 +90,7 @@ appId: xyz.blueskyweb.app
|
|||||||
- assertVisible: ".*Reply 1.*"
|
- assertVisible: ".*Reply 1.*"
|
||||||
- assertNotVisible: ".*Reply 2.*"
|
- assertNotVisible: ".*Reply 2.*"
|
||||||
- assertNotVisible: ".*Reply 3.*"
|
- assertNotVisible: ".*Reply 3.*"
|
||||||
- assertVisible:
|
- tapOn: ".*Reply 1.*"
|
||||||
id: "feedItem-by-bob.test"
|
|
||||||
- tapOn:
|
|
||||||
id: "feedItem-by-bob.test"
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "postDropdownBtn"
|
id: "postDropdownBtn"
|
||||||
childOf:
|
childOf:
|
||||||
|
|||||||
@@ -9,10 +9,26 @@ import ExpoImageCropTool, {
|
|||||||
} from '@bsky.app/expo-image-crop-tool'
|
} from '@bsky.app/expo-image-crop-tool'
|
||||||
|
|
||||||
import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants'
|
import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants'
|
||||||
|
import {IS_ANDROID} from '#/env'
|
||||||
import {compressIfNeeded} from './manip'
|
import {compressIfNeeded} from './manip'
|
||||||
import {type PickerImage} from './picker.shared'
|
import {type PickerImage} from './picker.shared'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Android emulator can't reach the iOS simulator's sample photo library,
|
||||||
|
* so run-nightly-e2e.sh seeds a single jpg into the app's external files
|
||||||
|
* directory before the flows run. That directory is world-readable via the
|
||||||
|
* emulator's sdcardfs, so expo-file-system can read it without any runtime
|
||||||
|
* media permission (unlike /sdcard/DCIM, which is gated behind scoped storage
|
||||||
|
* on target SDK 35).
|
||||||
|
*/
|
||||||
|
const ANDROID_E2E_MEDIA_DIR =
|
||||||
|
'file:///sdcard/Android/data/xyz.blueskyweb.app/files/e2e'
|
||||||
|
|
||||||
async function getFile() {
|
async function getFile() {
|
||||||
|
if (IS_ANDROID) {
|
||||||
|
return await getAndroidFile()
|
||||||
|
}
|
||||||
|
|
||||||
const imagesDir = documentDirectory!
|
const imagesDir = documentDirectory!
|
||||||
.split('/')
|
.split('/')
|
||||||
.slice(0, -6)
|
.slice(0, -6)
|
||||||
@@ -41,6 +57,34 @@ async function getFile() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getAndroidFile() {
|
||||||
|
let files = await readDirectoryAsync(ANDROID_E2E_MEDIA_DIR)
|
||||||
|
files = files.filter(file => file.toLowerCase().endsWith('.jpg'))
|
||||||
|
const file = `${ANDROID_E2E_MEDIA_DIR}/${files[0]}`
|
||||||
|
|
||||||
|
const fileInfo = await getInfoAsync(file)
|
||||||
|
|
||||||
|
if (!fileInfo.exists) {
|
||||||
|
throw new Error('Failed to get file info')
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dimensions of the seeded asset (assets/images/welcome-modal-bg.jpg). Only
|
||||||
|
* used for downstream aspect-ratio display; the actual bytes are read from
|
||||||
|
* disk by compressIfNeeded.
|
||||||
|
*/
|
||||||
|
return await compressIfNeeded(
|
||||||
|
{
|
||||||
|
path: file,
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
size: fileInfo.size,
|
||||||
|
width: 1432,
|
||||||
|
height: 1025,
|
||||||
|
},
|
||||||
|
IMAGE_SIZE_CONFIG_2K_1MB,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export async function openPicker(): Promise<PickerImage[]> {
|
export async function openPicker(): Promise<PickerImage[]> {
|
||||||
return [await getFile()]
|
return [await getFile()]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user