Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
869966e
add testIDs and fix iOS navigation in Maestro flows
Isthisanmol Apr 11, 2026
ee3b44a
fix all iOS Maestro flows
Isthisanmol Apr 13, 2026
5534a01
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
Isthisanmol Apr 13, 2026
abbb0c1
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
mikib0 Apr 15, 2026
0262939
fix(maestro): use placeholder text for iOS login form inputs as a wor…
mikib0 Apr 15, 2026
a264d3d
fix(maestro): update iOS input text for E2E tests
mikib0 Apr 15, 2026
77d894f
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
mikib0 Apr 16, 2026
4e8371e
fix(meastro): ios create trip flow
mikib0 Apr 16, 2026
88f9e26
chore(ci/e2e): set APP_ID to preview and run android tests via the bu…
mikib0 Apr 16, 2026
7d82003
chore(ci): temporarily set hardcoded test creds fallback
mikib0 Apr 16, 2026
2b3184d
fix(maestro): fix android test failures
mikib0 Apr 17, 2026
38f881b
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
mikib0 Apr 17, 2026
306ff9c
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
mikib0 Apr 23, 2026
caafc42
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
andrew-bierman Apr 23, 2026
58a51ef
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
mikib0 Apr 29, 2026
bf15a10
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
mikib0 May 1, 2026
390ef54
chore(e2e.sh): correct day formatting in date function for Android pl…
mikib0 May 1, 2026
12aa52e
chore(maestro): update test IDs for packs and trips flows to use new …
mikib0 May 1, 2026
6becbf4
fix(catalog): change createdAt and updatedAt fields to string with da…
mikib0 May 1, 2026
26dff98
chore(maestro): update testID for CatalogItemCard to use dynamic nami…
mikib0 May 1, 2026
eb993e9
chore(e2e.sh): remove hardcoded creds
mikib0 May 1, 2026
383df5a
chore(catalog): change createdAt and updatedAt fields back to string …
mikib0 May 1, 2026
42bc875
chore(e2e.sh): fix EOF error
mikib0 May 1, 2026
6ae2155
Merge branch 'development' into fix/Maestro-E2E-flows-for-iOS
mikib0 May 1, 2026
09fecf9
chore(maestro/login-flow): improve stability of post-login navigation…
mikib0 May 4, 2026
8f69f89
Merge remote-tracking branch 'origin/development' into fix/Maestro-E2…
mikib0 May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions .github/scripts/e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,23 @@ set -e
PLATFORM=$1 # "ios" or "android"
shift # Remove first argument so $@ contains only the additional options

# Require test credentials to be supplied via env (CI secrets or local .env.local).
# Never fall back to baked-in defaults.
if [ -z "${TEST_EMAIL:-}" ] || [ -z "${TEST_PASSWORD:-}" ]; then
echo "ERROR: TEST_EMAIL and TEST_PASSWORD must be set (via CI secrets or a gitignored .env.local)" >&2
exit 1
fi

# Generate unique ID for this test run
UNIQUE_ID=$(date +%s)

if [ "$PLATFORM" = "ios" ]; then
START_DATE=$(date -j -v+7d +"%Y-%m-%d")
END_DATE=$(date -j -v+14d +"%Y-%m-%d")

TODAY_DATE=$(date -j +"%b %-d, %Y") # e.g. "Apr 16, 2026"
get_month() { date -j -f "%Y-%m-%d" "$1" +"%B"; }
get_day() { date -j -f "%Y-%m-%d" "$1" +"%-d"; }
get_year() { date -j -f "%Y-%m-%d" "$1" +"%Y"; }
get_month_num() { date -j -f "%Y-%m-%d" "$1" +"%-m"; }
else
START_DATE=$(date -d "+7 days" +"%Y-%m-%d")
END_DATE=$(date -d "+14 days" +"%Y-%m-%d")

TODAY_DATE=$(date +"%-d %b %Y")
get_month() { date -d "$1" +"%B"; }
get_day() { date -d "$1" +"%-d"; }
get_day() { date -d "$1" +"%d"; }
get_year() { date -d "$1" +"%Y"; }
get_month_num() { date -d "$1" +"%-m"; }
fi
Expand All @@ -40,8 +33,8 @@ END_TAPS=$(( ($(get_year "$END_DATE") - CURRENT_YEAR) * 12 + ($(get_month_num "$

if [ "$PLATFORM" = "ios" ]; then
maestro test --config .maestro/config.yaml "$@" \
-e TEST_EMAIL="$TEST_EMAIL" \
-e TEST_PASSWORD="$TEST_PASSWORD" \
-e TEST_EMAIL="${TEST_EMAIL}" \
-e TEST_PASSWORD="${TEST_PASSWORD}" \
-e TRIP_NAME="${TRIP_NAME:-E2E-Trip-$UNIQUE_ID}" \
-e PACK_NAME="${PACK_NAME:-E2E-Pack-$UNIQUE_ID}" \
-e APP_ID="${APP_ID:-com.andrewbierman.packrat.preview}" \
Expand All @@ -53,11 +46,12 @@ if [ "$PLATFORM" = "ios" ]; then
-e END_MONTH="$(get_month "$END_DATE")" \
-e END_DAY="$(get_day "$END_DATE")" \
-e END_TAPS="$END_TAPS" \
-e TODAY_DATE="$TODAY_DATE" \
.maestro/master-flow.yaml
else
maestro test --config .maestro/config-android.yaml "$@" \
-e TEST_EMAIL="$TEST_EMAIL" \
-e TEST_PASSWORD="$TEST_PASSWORD" \
-e TEST_EMAIL="${TEST_EMAIL}" \
-e TEST_PASSWORD="${TEST_PASSWORD}" \
-e TRIP_NAME="${TRIP_NAME:-E2E-Trip-$UNIQUE_ID}" \
-e PACK_NAME="${PACK_NAME:-E2E-Pack-$UNIQUE_ID}" \
-e APP_ID="${APP_ID:-com.packratai.mobile.preview}" \
Expand Down
14 changes: 3 additions & 11 deletions .maestro/flows/auth/login-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ appId: ${APP_ID}
id: "sign-in-email-button"

# Wait for login form to appear.
# auth/(login) is presented as a modal; waitForAnimationToEnd alone is not
# sufficient — the XCTest accessibility tree may not include the modal until
# the presentation animation fully settles. extendedWaitUntil polls until
# the field is accessible, giving the modal time to fully render.
# iOS 26 / slow CI: the app startup takes ~10s before sign-in-button appears;
# by the time the modal opens we may have little budget left. Use 35s here.
- runFlow:
Expand All @@ -44,10 +40,7 @@ appId: ${APP_ID}
timeout: 35000
- waitForAnimationToEnd

# Fill in the email field.
# iOS: NativeWindUI's TextField inside accessible={false} FormSection doesn't expose
# testID to Maestro's XCTest snapshot, so we match the placeholder text instead.
# Android: testID maps reliably to resource-id.
# Fill in the email field
- runFlow:
when:
platform: iOS
Expand Down Expand Up @@ -89,8 +82,7 @@ appId: ${APP_ID}
# Wait for navigation to complete — login can take a few seconds
- waitForAnimationToEnd

# Handle transient network error: if the API call failed (e.g. "Network request
# failed" dialog), dismiss the alert and retry once with the same credentials.
# Handle transient network error: if the API call failed, dismiss the alert and retry once.
- runFlow:
when:
visible:
Expand Down Expand Up @@ -125,4 +117,4 @@ appId: ${APP_ID}
- extendedWaitUntil:
visible:
text: "Packs"
timeout: 35000
timeout: 35000
68 changes: 14 additions & 54 deletions .maestro/flows/auth/logout-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,29 @@ appId: ${APP_ID}

- waitForAnimationToEnd

# Scroll down to find the sign-out button.
# iOS: use testID (id:) to avoid UITextView accessibilityValue issues with text-based matching.
# Android: use text: which works reliably (UITextView issue is iOS-only).
- runFlow:
when:
platform: iOS
commands:
- scrollUntilVisible:
element:
id: "sign-out-button"
direction: DOWN
- tapOn:
id: "sign-out-button"
- runFlow:
when:
platform: Android
commands:
- scrollUntilVisible:
element:
text: "Log Out"
direction: DOWN
- tapOn:
text: "Log Out"
# Scroll down to find the log out button if needed
- scrollUntilVisible:
element:
text: "Log Out"
direction: DOWN

# Tap Log Out
- tapOn:
text: "Log Out"

- waitForAnimationToEnd

# Handle sync-conflict confirmation dialog if present (only appears when there are unsynced changes).
# Both platforms use native Alert.alert(). With snapshotKeyHonorModalViews:false the main-window
# elements appear first in the snapshot tree, so tapOn:text:"Log Out" (any index) hits the
# profile list button (resource-id=sign-out-button, y=706) instead of the alert action.
# Fix: use rightOf:text:"Cancel" to uniquely identify the dialog "Log Out" — "Cancel" only
# exists inside the dialog, so this selector picks the adjacent alert action button.
# waitForAnimationToEnd ensures the native alert is fully settled before we interact.
- runFlow:
when:
visible:
text: "Sync in progress"
commands:
- waitForAnimationToEnd
- tapOn:
text: "Log Out"
rightOf:
text: "Cancel"
text: "Proceed"

- waitForAnimationToEnd

# Handle post-logout dialog if it appears - choose to Sign-in again
# Handle post-logout dialog - choose to Sign-in again
- runFlow:
when:
visible:
Expand All @@ -66,22 +42,6 @@ appId: ${APP_ID}

- waitForAnimationToEnd

# Assert we are back on the auth screen (allow up to 30s for app reload).
# iOS: use testID to avoid UITextView text-matching issues on the Sign In button.
# Android: use text: which works reliably.
- runFlow:
when:
platform: iOS
commands:
- extendedWaitUntil:
visible:
id: "sign-in-email-button"
timeout: 30000
- runFlow:
when:
platform: Android
commands:
- extendedWaitUntil:
visible:
text: "Sign In"
timeout: 30000
# Assert we are back on the auth screen
- assertVisible:
text: "Sign In"
30 changes: 11 additions & 19 deletions .maestro/flows/catalog/catalog-browse-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,21 @@ appId: ${APP_ID}
---
# Catalog Browse Flow: Verify catalog tab loads items and categories
- waitForAnimationToEnd

# Navigate to Catalog tab
- tapOn:
text: "Catalog"
- waitForAnimationToEnd

# Assert catalog header loaded
- assertVisible:
text: "Catalog"

# Wait for actual catalog items to load via testID.
# Previous text-based check (".*items.*") matched "0 items" during loading
# state without verifying data actually arrived. Use id: for reliability.
- extendedWaitUntil:
visible:
id: "catalog-item-card"
timeout: 30000

# Assert category filters render
- assertVisible:
text: "All"

# Intentionally stay on Catalog tab.
# catalog-item-detail-flow runs next and starts with tapOn:"Catalog" which
# is a scroll-to-top on the already-active tab — items remain loaded and
# no fresh API call is needed.
- assertVisible:
text: ".*items.*"
- scrollUntilVisible:
element:
text: "Showing.*items"
direction: DOWN
timeout: 10000
speed: 20
Comment on lines +12 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Locale-dependent text matching makes this flow fragile.

.*items.* and Showing.*items depend on English copy. The screen text is translation-driven, so this can break with locale changes. Prefer testID-based assertions for the count/showing labels.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.maestro/flows/catalog/catalog-browse-flow.yaml around lines 12 - 19, The
locale-dependent regex selectors used in assertVisible and scrollUntilVisible
(the text fields ".*items.*" and "Showing.*items") are fragile; update these
steps to target stable testIDs instead (e.g., replace assertVisible:text and
scrollUntilVisible:element.text with assertions that use element.testID or a
testID-specific selector), ensuring you reference the same logical UI elements
(the count/showing label) when modifying the assertVisible and
scrollUntilVisible steps so the flow stops relying on English copy.

- tapOn:
text: "Packs"
- waitForAnimationToEnd
74 changes: 20 additions & 54 deletions .maestro/flows/catalog/catalog-item-detail-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,43 @@ appId: ${APP_ID}
---
# Catalog Item Detail Flow: Tap a catalog item and verify detail page
- waitForAnimationToEnd

# Navigate to Catalog tab (scroll-to-top if already active; normal tab tap otherwise)
- tapOn:
text: "Catalog"
- waitForAnimationToEnd

# iOS: returning to the Catalog tab can restore the search bar as first responder,
# overlaying the screen and suppressing the FlatList items from the accessibility tree.
# Dismiss it if visible.
- runFlow:
when:
platform: iOS
commands:
- runFlow:
when:
visible:
text: "Cancel"
commands:
- tapOn:
text: "Cancel"
- waitForAnimationToEnd

# Wait for catalog items to load via testID.
# catalog-browse-flow already loaded items (30s wait) so these should appear
# quickly. Use a generous timeout to handle the case where this flow is run
# standalone (fresh load).
- extendedWaitUntil:
visible:
id: "catalog-item-card"
timeout: 30000

# Tap the first visible catalog item by testID.
# Wait for items to load
- assertVisible:
text: ".*items.*"
# Tap first visible item using index
- tapOn:
id: "catalog-item-card"
id: "catalog:item-.*"
index: 0
Comment on lines +9 to +14

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wait for the target element ID before tapping.

assertVisible on .*items.* can pass before cards are interactable (and is copy-dependent). Wait for id: "catalog-item" directly, then tap.

Proposed change
-- assertVisible:
-    text: ".*items.*"
+- assertVisible:
+    id: "catalog-item"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.maestro/flows/catalog/catalog-item-detail-flow.yaml around lines 9 - 14,
Replace the loose assertVisible on ".*items.*" with a direct wait for the target
element id before tapping: change the step that currently uses assertVisible
(text: ".*items.*") to wait for the element with id "catalog-item" (the same id
used in the tapOn step), then perform tapOn with id: "catalog-item" and index: 0
so the tap only occurs after the specific card is present and interactable.

- waitForAnimationToEnd

# Wait for the detail screen container to confirm navigation happened.
# The add-to-pack button is below the fold in a ScrollView; on iOS XCTest
# does not expose off-screen ScrollView children in the accessibility tree
# until they are scrolled into view.
# Use 30s: CI network latency can delay initial data load past 15s.
- extendedWaitUntil:
visible:
id: "catalog-detail-content"
timeout: 30000

# Scroll the detail screen to bring the action buttons into view.
# Scroll down to find action buttons
- scrollUntilVisible:
element:
id: "add-to-pack-button"
direction: DOWN
timeout: 15000

- assertVisible:
id: "add-to-pack-button"
- scrollUntilVisible:
element:
id: "view-retailer-button"
direction: DOWN
speed: 10
timeout: 10000
- assertVisible:
id: "view-retailer-button"

# Go back
# iOS: XCTest synthetic swipes do not trigger UIScreenEdgePanGestureRecognizer.
# Tap the native navigation bar back button instead (accessibility label "Back").
# Go back - platform specific
- runFlow:
when:
platform: iOS
platform: Android
commands:
- tapOn:
text: "Back"
- waitForAnimationToEnd
- back
- runFlow:
when:
platform: Android
platform: iOS
commands:
- back
- waitForAnimationToEnd
- tapOn:
text: ".*Back.*"
Comment on lines +38 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The iOS back step is brittle with text: ".*Back.*".

This assumes a visible English back label, which is not guaranteed. Add a deterministic back control testID in the screen/header and tap by id here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.maestro/flows/catalog/catalog-item-detail-flow.yaml around lines 39 - 44,
The iOS back step in runFlow currently taps by text using tapOn with text:
".*Back.*", which is brittle; instead add a deterministic testID to the back
control in the screen/header (e.g., headerBackButton testID) and change the
runFlow tap step to use tapOn with id: "<headerBackButton-id>" (replace with the
actual testID); update the tapOn invocation in the catalog-item-detail-flow to
reference that id so tests tap the header back control deterministically.

- waitForAnimationToEnd
20 changes: 14 additions & 6 deletions .maestro/flows/dashboard/dashboard-tiles-flow.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
appId: ${APP_ID}
---
# Dashboard Tiles Flow: Verify dashboard tab loads
# Dashboard Tiles Flow: Verify dashboard loads with key tiles
- waitForAnimationToEnd

# Navigate to Dashboard tab
# Navigate to Dashboard (home tab)
- tapOn:
text: "Dashboard"
Comment on lines 7 to +8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid localized text selector for dashboard tab tap.

Line 8 taps by "Dashboard" text, but the tab label is translation-driven; this can fail when locale changes. Prefer a stable id/testID selector for the tab and tap by id in this flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.maestro/flows/dashboard/dashboard-tiles-flow.yaml around lines 7 - 8, The
tap action currently uses a locale-dependent text selector (tapOn: text:
"Dashboard"); change it to use a stable identifier by replacing the text
selector with an id/testID selector (e.g., tapOn: id: "dashboard-tab" or tapOn:
testID: "dashboardTab") and update the app code to expose that id if it doesn't
exist; keep the same tapOn key and only swap the selector type so the flow
reliably taps the Dashboard tab across locales.

- waitForAnimationToEnd

# Verify we landed on the Dashboard screen (large-title header).
# NativeWindUI ListItem tile titles are not exposed as individual nodes in
# the iOS accessibility tree, so we only assert on the screen header here.
# Verify key dashboard tiles are present
- scrollUntilVisible:
element:
id: "dashboard-tile-packrat-ai"
direction: DOWN
- assertVisible:
text: "Dashboard"
id: "dashboard-tile-packrat-ai"

# Scroll to check more tiles
- scrollUntilVisible:
element:
text: ".*Pack.*"
direction: DOWN

# Return to stable state
- tapOn:
Expand Down
Loading
Loading