Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
210 changes: 147 additions & 63 deletions apps/mobile/.maestro/20-account-creation-journey.yaml
Original file line number Diff line number Diff line change
@@ -1,31 +1,61 @@
appId: ${APP_ID}
name: Account creation — end-to-end user journey (sign-in → CreateProfile → CompleteProfile → AskForLocation → Swipe)
name: Account creation — fresh signup → CreateProfile → CompleteProfile → AskForLocation → Swipe
tags:
- smoke
- regression
---
# Full account-creation journey for a brand-new user. Replaces the disjoint
# 03/04/05 smoke flows with one realistic, assert-driven walk:
# Full account-creation user journey for a brand-new user.
#
# Replaces the disjoint 03/04/05 smoke flows with one realistic, assert-driven
# walk that mirrors what a real first-time user does:
#
# 1. Cold-launch with cleared state + keychain + pre-granted permissions
# (photos:all, location:inuse, notifications:allow). Pre-granting from
# launchApp removes three system dialogs from the critical path — Maestro
# still has `optional: true` fallbacks below for the cases where iOS
# decides to re-prompt anyway (real on iOS 26 Release builds).
#
# 1. Cold launch with cleared state + keychain + pre-granted permissions
# (photos:all, location:inuse) so no system dialog can interrupt the run.
# 2. Sign in via utils/login-fresh.yaml — the API's APPLE_MAGIC_EMAIL_REGEX
# bypass guarantees the user is hard-purged on every login so the auth
# router always lands on CreateProfile (see AuthenticationService.ts).
# 3. CreateProfile: upload one photo from the simulator's stock library,
# type the dog name, submit.
# 4. CompleteProfile: enter a birthdate, submit.
# 5. AskForLocation: tap Enable Location; any system dialog is auto-allowed
# via the launchApp permissions block, but we also tap "Allow While Using
# App" defensively in case the OS still prompts.
# 6. Swipe tab: assertVisible on `swipe-screen` testID (added to the Swipe
# view's outer Container) to prove we reached the tabs.
# bypass (^maestro-fresh.*@pegada\.app$ — see
# packages/api/src/services/AuthenticationService.ts → isFreshMagicEmail)
# hard-purges the matching user + cascading Dog/Image/Match/Interest/
# Message rows BEFORE upserting, so the auth router always lands on
# CreateProfile regardless of any prior run's state. Two back-to-back
# runs are safe — the delete-then-upsert keeps state clean.
#
# 3. CreateProfile (apps/mobile/src/views/(auth)/CreateProfile/index.tsx):
# single screen with photos grid (ProfileImagesUploader → AddUserPhoto),
# dog-name TextInput, optional bio, and gender RadioButtons (defaults
# to MALE). Submit requires ≥1 image + name.
#
# 4. CompleteProfile (apps/mobile/src/views/(auth)/CompleteProfile/index.tsx):
# requires a birthdate. We type into the masked text input directly.
#
# 5. AskForLocation: tap Enable Location → defensively dismiss any system
# prompt that still appears even with location:inuse pre-granted.
#
# Verification is real — `assertVisible` on stable testIDs, plus a screenshot
# at every major transition for hash-diff review.
# 6. Swipe tab: assertVisible on `swipe-screen` testID — the SwipeScreen
# registers as a native a11y container (the SafeAreaView wrapper) that
# XCUITest CAN see, unlike the deeply-nested Input/Pressable RN nodes
# below.
#
# Coordinate-based taps are used ONLY where a testID is genuinely unreachable
# (e.g. the iOS native photo-picker UI). Every in-app target uses a testID.
# iOS 26 Fabric reality (verified empirically on this branch — see
# utils/login-fresh.yaml comments and the run that produced
# ~/.maestro/tests/2026-05-21_164009):
# - XCUITest's accessibility snapshot is BLIND to most RN-rendered
# content for this app build: `tapOn: id: profile-name` times out
# looping through an empty hierarchy. We use point-based taps for
# every in-app TextInput / Button on CreateProfile + CompleteProfile.
# - Native system UI (image picker alert, photos library, location
# prompt) IS visible via native a11y labels (text: "Choose from
# Library" etc.).
# - Coordinates calibrated on iPhone 17 Pro Max (440x956 logical,
# 1320x2868 px). The same reference device used by login-fresh.yaml
# and the rest of the suite.
#
# Photo upload regression fix (PR #36): on iOS 26 Release builds the
# presigned PUT used to time out before completing — the 15s wait below
# is the empirical ceiling on slow CI runners (local sim completes in ~2s).

- launchApp:
clearState: true
Expand All @@ -37,53 +67,85 @@ tags:
- waitForAnimationToEnd

# ---------------------------------------------------------------------------
# 1. Sign in as a fresh user (lands on CreateProfile)
# 1. Sign in as a fresh user lands on CreateProfile
# ---------------------------------------------------------------------------
# utils/login-fresh.yaml drops the user on CreateProfile every time via the
# regex-magic bypass. It also dismisses the ATT prompt internally.
- takeScreenshot: 20-01-on-email
- runFlow: utils/login-fresh.yaml
- waitForAnimationToEnd:
timeout: 15000
- takeScreenshot: 20-01-after-login
- takeScreenshot: 20-02-on-create-profile

# ---------------------------------------------------------------------------
# 2. CreateProfile — upload one photo + type dog name + submit
# 2. CreateProfile — upload one photo, then type dog name, then submit
# ---------------------------------------------------------------------------
# Tap the first photo slot. testID `add-photo-0` is on the PressableArea
# inside ProfileImageUploader → AddUserPhoto (added with this flow).
# Photo first: the picker dismisses the keyboard anyway, so doing it before
# typing avoids the focus-shuffling that breaks Maestro driver state on
# iOS 26. The `add-photo-0` testID is on AddUserPhoto's PressableArea but
# XCUITest cannot see it on this build — point tap targets the top-left
# photo cell of the 2-column grid inside ProfileImagesUploader. The cell
# sits below the "Profile Pictures" header and "Add at least one photo"
# subtitle; on the freshly-loaded CreateProfile screen (no keyboard) the
# first column sits at roughly 23% X, 27% Y on iPhone 17 Pro Max.
- tapOn:
id: "add-photo-0"
point: "23%, 27%"
- waitForAnimationToEnd:
timeout: 5000
- takeScreenshot: 20-02-image-picker-options

# The image picker shows a native Alert with: Take Photo / Choose from Library
# / Cancel. Choose from Library is the deterministic path on iOS sim, which
# always seeds 4 stock photos in the Photos app.
# Image picker shows a native Alert with: Take Photo / Choose from Library
# / Cancel. The native Alert IS visible via text labels.
- tapOn:
text: "Choose from Library"
- waitForAnimationToEnd:
timeout: 8000

# Photo library may show a "Select Photos" permission sheet on iOS 14+ even
# when `photos: all` was pre-granted, depending on the runtime — dismiss
# defensively. The most common state is "all access granted, no sheet".
# Photo library shows a "Pegada would like full access to your Photo
# Library" permission sheet on iOS 26 EVEN when `photos: all` was
# pre-granted via launchApp.permissions — iOS 26 deliberately re-prompts
# the user the first time the app opens the picker (verified empirically:
# `maestro hierarchy` after the "Choose from Library" tap showed the
# sheet's "Allow Full Access" / "Limit Access…" / "Don't Allow" buttons
# even with photos:all pre-granted).
#
# The sheet animates in a fraction of a second AFTER the picker opens;
# extendedWaitUntil with `optional: true` gives it up to 5s to render
# before we attempt the tap. Without this, the optional tapOn returns
# immediately if the sheet hasn't drawn yet, and we end up tapping
# blindly into the picker grid before access is granted (= permission
# revoked, photo selection silently no-ops). The label string changed
# between iOS versions, so we cover both legacy ("Allow Access to All
# Photos") and iOS 26 ("Allow Full Access").
- extendedWaitUntil:
visible:
text: "Allow Full Access"
timeout: 15000
optional: true
- tapOn:
text: "Allow Access to All Photos"
text: "Allow Full Access"
optional: true
- tapOn:
text: "Select Photos..."
text: "Allow Access to All Photos"
optional: true
- waitForAnimationToEnd
- waitForAnimationToEnd:
timeout: 8000

# Tap the first photo in the grid. The iOS simulator photo library is a
# native picker UI — no testIDs available. Coordinates target the first cell
# of the 4-up grid (top-left, ~25% X / ~30% Y on iPhone 17 Pro Max).
# Tap the first photo in the picker grid. iOS 26 renders the photo
# library as a HALF-SHEET (verified via `maestro hierarchy` after the
# Choose-from-Library tap: sheet bounds [60,200][380,783] on iPhone 17
# Pro Max). Inside the sheet, the photo grid is 3 columns × N rows
# below a small search/nav header. First row of cells centers at
# roughly Y=290 (sheet-relative ~90), X=113 (left cell) / 220 (middle)
# / 327 (right). In screen percentages: ~26%, 50%, 74% X; Y≈30%.
# Targeting the middle cell of the first row maximises the chance of
# hitting an actual photo even if the grid layout shifts slightly.
- tapOn:
point: "20%, 30%"
point: "50%, 30%"
- waitForAnimationToEnd:
timeout: 5000
- takeScreenshot: 20-03-photo-edit

# Edit screen: tap "Choose" (bottom-right) to confirm. Native UI again.
# Edit screen: "Choose" (bottom-right) on iOS 16+; older sims show "Use".
# Both are optional because some builds skip the edit step entirely.
- tapOn:
text: "Choose"
optional: true
Expand All @@ -93,50 +155,72 @@ tags:
- waitForAnimationToEnd:
timeout: 15000

# Wait for the S3 upload to complete — the ProfileImagesUploader shows an
# ActivityIndicator overlay until the presigned PUT returns. 15s ceiling
# covers a slow CI runner; locally completes in ~2s.
- takeScreenshot: 20-04-photo-uploaded
# Wait for the S3 upload — ProfileImagesUploader shows an
# ActivityIndicator overlay until the presigned PUT returns. 15s covers
# the iOS 26 Release-build regression fixed in PR #36; local sim
# completes in ~2s.
- takeScreenshot: 20-05-photo-uploaded

# Tap the dog-name input via testID (exists on CreateProfile).
# Dog-name TextInput. With the photo grid filled at the top, the name
# Input center sits at roughly 50%, 55% Y on iPhone 17 Pro Max (form is
# Photos header → Photo grid → Photo footer → Name title → Name input).
# This matches the legacy 03-create-profile.yaml coord (50%, 66%) shifted
# up by the now-visible photo grid taking less vertical space than the
# placeholder slots.
- tapOn:
id: "profile-name"
point: "50%, 55%"
- waitForAnimationToEnd
- inputText: "MaestroPup"
- takeScreenshot: 20-05-name-typed
- takeScreenshot: 20-03-after-name

# Submit. testID is shared by Create/Complete profile Buttons; only one is
# mounted at a time so this is unambiguous.
# Dismiss keyboard so the Submit button sits at its keyboard-down
# position. hideKeyboard is a no-op on iOS sometimes — tap a safe area
# (above the form) as a fallback.
- hideKeyboard
- waitForAnimationToEnd

# CreateProfile button. The BottomAction.Container pins it to the bottom
# of the screen at ~50%, 92% Y on iPhone 17 Pro Max (BottomAction adds
# safe-area padding so the button center lands above the home indicator).
- tapOn:
id: "profile-submit"
point: "50%, 92%"
- waitForAnimationToEnd:
timeout: 15000
- takeScreenshot: 20-06-after-create-profile

# ---------------------------------------------------------------------------
# 3. CompleteProfile — pick birthdate + submit
# ---------------------------------------------------------------------------
# Birthdate TextInput on CompleteProfile is the only required field — the
# screen has profile-photo preview + birthdate input + submit. Birthdate
# input center is at ~50%, 55% Y (matches legacy 04-complete-profile.yaml).
- tapOn:
id: "complete-profile-birth-date"
point: "50%, 55%"
- waitForAnimationToEnd
- inputText: "01/01/2020"
- takeScreenshot: 20-07-birthdate-typed
- takeScreenshot: 20-04-after-birthdate

- hideKeyboard
- waitForAnimationToEnd

- tapOn:
id: "profile-submit"
point: "50%, 92%"
- waitForAnimationToEnd:
timeout: 12000
- takeScreenshot: 20-08-after-complete-profile

# ---------------------------------------------------------------------------
# 4. AskForLocation — tap Enable Location
# 4. AskForLocation — tap Enable Location and accept the system prompt
# ---------------------------------------------------------------------------
- takeScreenshot: 20-06-on-location
# Enable Location button is the primary CTA at ~50%, 92% Y (BottomAction
# container, same vertical position as the submit buttons above).
- tapOn:
id: "location-allow"
point: "50%, 92%"
- waitForAnimationToEnd:
timeout: 5000

# Defensive: if iOS still pops the location prompt (sim quirk), accept it.
# Defensive: if iOS still pops the location prompt (sim quirk with
# pre-granted permissions on iOS 26), accept it. Both labels are
# present in different iOS versions; either path is fine for the test.
- tapOn:
text: "Allow While Using App"
optional: true
Expand All @@ -145,14 +229,14 @@ tags:
optional: true
- waitForAnimationToEnd:
timeout: 15000
- takeScreenshot: 20-09-after-location

# ---------------------------------------------------------------------------
# 5. Final assertion — we landed on the Swipe tab
# ---------------------------------------------------------------------------
# `swipe-screen` testID is on the outer SafeAreaView Container in
# src/views/(tabs)/Swipe/index.tsx — added with this flow so it survives
# even when the user has zero cards (fresh account → empty stack).
# apps/mobile/src/views/(tabs)/Swipe/index.tsx — this one IS visible to
# XCUITest because it's the top-level a11y container. Survives even when
# the fresh user has zero cards (empty-state UI mounted).
- assertVisible:
id: "swipe-screen"
- takeScreenshot: 20-10-on-swipe-tab
- takeScreenshot: 20-07-on-swipe
Loading
Loading