Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5661087
feat(api): local Postgres + wrangler dev stack for Maestro e2e
mikib0 May 20, 2026
b85ecd2
feat(ci): local Postgres + wrangler dev for Maestro e2e workflow
mikib0 May 20, 2026
ade4c49
fix(api): local e2e stack - pgvector image, auth DB routing, credenti…
mikib0 May 20, 2026
1988234
fix(api): set idleTimeoutMillis=0 on pg.Pool for Workers runtime comp…
mikib0 May 20, 2026
888ba0d
fix(e2e): handle Expo dev-client overlays and KV TTL floor in local e…
mikib0 May 20, 2026
a0d03cb
chore: sort packages/api/package.json keys
mikib0 May 20, 2026
d29f4f3
fix(api): reuse auth instance across requests in miniflare
mikib0 May 20, 2026
04aa314
fix(ci): fix e2e pgvector extension and iOS postgres setup failures
mikib0 May 21, 2026
59d4951
fix(ci): fix wrangler PATH and pgvector build for e2e local API stack
claude May 21, 2026
02a25aa
fix(e2e): force wrangler dev into local mode and fix kv_namespaces en…
mikib0 May 21, 2026
d94e882
fix(ci): fix pgvector superuser permission on iOS and Node.js version…
mikib0 May 21, 2026
3285a9e
fix(ci): disable wrangler containers in e2e — Docker not available on…
mikib0 May 21, 2026
f9e01cd
fix(ci): install Docker via Colima on macOS runner for wrangler conta…
mikib0 May 21, 2026
e4b28d5
fix(ci): use QEMU backend for Colima — VZ is blocked on macOS-15 runners
mikib0 May 21, 2026
a97207d
fix(ci): disable containers on iOS job — macOS runners have no hyperv…
mikib0 May 21, 2026
0af518e
fix: address PR review comments
mikib0 May 21, 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
404 changes: 335 additions & 69 deletions .github/workflows/e2e-tests.yml

Large diffs are not rendered by default.

201 changes: 131 additions & 70 deletions .maestro/flows/auth/login-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,104 +17,165 @@ appId: ${APP_ID}
text: "Wait"
- waitForAnimationToEnd

# Wait for the auth entry screen — ensures sign-in button is rendered before tapping
- extendedWaitUntil:
visible:
id: "sign-in-email-button"
timeout: 15000

# Tap the "Sign In" (email) button to navigate to the email login screen
- tapOn:
id: "sign-in-email-button"

# Wait for login form to appear.
# 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:
when:
platform: iOS
commands:
- extendedWaitUntil:
visible:
text: "Email"
timeout: 35000
# Give the app time to render its initial screen before checking for overlays.
- waitForAnimationToEnd

# Fill in the email field
- runFlow:
when:
platform: iOS
commands:
- tapOn:
text: "Email"
- inputText: "${TEST_EMAIL}"
# Dev client only: if the Metro bundle failed to load (e.g. server was unreachable),
# dismiss the "Error loading app" alert so we can retry the dev-server picker.
- runFlow:
when:
platform: Android
visible:
text: "Error loading app"
commands:
- tapOn:
id: "email-input"
- inputText: "${TEST_EMAIL}"
text: "OK"
- waitForAnimationToEnd

# Fill in the password field
# Dev client only: if the dev server picker appears on iOS (saved Metro URL was
# cleared or is stale), tap the discovered server row identified by METRO_HOST:METRO_PORT.
- runFlow:
when:
platform: iOS
commands:
- tapOn:
text: "Password"
- inputText: ${TEST_PASSWORD}
- runFlow:
when:
visible:
text: "DEVELOPMENT SERVERS"
commands:
- waitForAnimationToEnd
- tapOn:
text: "http://${METRO_HOST}:${METRO_PORT}"
- waitForAnimationToEnd

Comment on lines +34 to +49
# Dev client only: dismiss the Expo "Developer Tools" intro + dev-menu sheet that
# appears on the first launch after clearState.
# Step 1: Tap "Continue" on the intro card to advance past it (opens the dev menu sheet).
# Step 2: Tap the scrim above the sheet to dismiss the dev menu.
- runFlow:
when:
platform: Android
visible:
text: "Continue"
commands:
- tapOn:
id: "password-input"
- inputText: ${TEST_PASSWORD}

# Dismiss the keyboard before submitting
- hideKeyboard

# Submit login form via the continue button
- tapOn:
id: "continue-button"

# Wait for navigation to complete — login can take a few seconds
- waitForAnimationToEnd
text: "Continue"
- waitForAnimationToEnd
- runFlow:
when:
visible:
text: "Reload"
commands:
- tapOn:
point: "50%,10%"
- waitForAnimationToEnd

# Handle transient network error: if the API call failed, dismiss the alert and retry once.
# Sign in only if the auth entry screen is shown.
# Maestro's clearState does NOT clear iOS Keychain / SecureStore, so on runs
# after a successful login the session token survives and the app opens straight
# to Dashboard. Wrapping the entire sign-in block in a `runFlow when visible`
# lets both cases (needs login / already logged in) reach the Packs assertion.
- runFlow:
when:
visible:
text: "Network request failed"
id: "sign-in-email-button"
commands:
# Tap the "Sign In" (email) button to navigate to the email login screen
- tapOn:
text: "OK"
id: "sign-in-email-button"

# Wait for login form to appear.
# 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:
when:
platform: iOS
commands:
- extendedWaitUntil:
visible:
text: "Email"
timeout: 35000
- waitForAnimationToEnd

# Fill in the email field
- runFlow:
when:
platform: iOS
commands:
- tapOn:
text: "Email"
- inputText: "${TEST_EMAIL}"
- runFlow:
when:
platform: Android
commands:
- tapOn:
id: "email-input"
- inputText: "${TEST_EMAIL}"

# Fill in the password field
- runFlow:
when:
platform: iOS
commands:
- tapOn:
text: "Password"
- inputText: ${TEST_PASSWORD}
- runFlow:
when:
platform: Android
commands:
- tapOn:
id: "password-input"
- inputText: ${TEST_PASSWORD}

# Dismiss the keyboard before submitting
- hideKeyboard

# Submit login form via the continue button
- tapOn:
id: "continue-button"
- waitForAnimationToEnd

# iOS only: dismiss the system "Save Password?" Keychain prompt that appears
# after submitting any form with a password field (textContentType="password").
# This prompt is a blocking OS-level dialog and would intercept subsequent taps
# if not handled. Real users can choose to save — in CI we always skip it.
- runFlow:
when:
platform: iOS
commands:
- tapOn:
text: "Not Now"
optional: true
label: "Dismiss iOS Save Password prompt"
# Wait for the API call to complete: the button shows "Loading..." during the
# network round-trip. waitForAnimationToEnd returns as soon as the screen is
# visually stable, but "Loading..." is static — wait explicitly for it to disappear.
- extendedWaitUntil:
notVisible:
text: "Loading.*"
timeout: 20000

# Handle transient errors: dismiss the alert and retry once for any failure dialog.
- runFlow:
when:
visible:
text: "Login Failed"
commands:
- tapOn:
text: "OK"
- waitForAnimationToEnd
- tapOn:
id: "continue-button"
- extendedWaitUntil:
notVisible:
text: "Loading.*"
timeout: 20000

# iOS only: dismiss the system "Save Password?" Keychain prompt that appears
# after submitting any form with a password field (textContentType="password").
# This prompt is a blocking OS-level dialog and would intercept subsequent taps
# if not handled. Real users can choose to save — in CI we always skip it.
- runFlow:
when:
platform: iOS
commands:
- tapOn:
text: "Not Now"
optional: true
label: "Dismiss iOS Save Password prompt"

# Wait for the main tab bar to appear — confirms we are logged in and on the main app.
# We only assert the tab bar is visible here; navigation to specific tabs is left to
# subsequent flows (dashboard-tiles-flow taps Packs, providing a stable entry point for
# create-pack-flow). Navigating to Packs directly after login is unreliable on iOS
# because Expo Router's post-login routing is still settling at this point.
# This assertion covers both flows: just signed-in AND already-authenticated (session
# survived clearState because Keychain is not wiped by Maestro's clearState on iOS).
- waitForAnimationToEnd
- extendedWaitUntil:
visible:
text: "Packs"
timeout: 35000
timeout: 35000
10 changes: 7 additions & 3 deletions .maestro/flows/packs/create-pack-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ appId: ${APP_ID}
# Create Pack Flow: Navigate to packs tab and create a new pack
- waitForAnimationToEnd

# Navigate to the Packs tab
# Navigate to the Packs tab and wait until the header create button is visible.
# Use extendedWaitUntil rather than a bare tap+wait to handle cases where the
# animation from the previous flow hasn't fully settled.
- tapOn:
text: "Packs"

- waitForAnimationToEnd
- extendedWaitUntil:
visible:
id: "create-pack-button"
timeout: 10000

# Tap the header "+" button (testID: create-pack-button) to open the pack creation form
- tapOn:
Expand Down
20 changes: 20 additions & 0 deletions .maestro/flows/setup/clear-state.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,23 @@ appId: ${APP_ID}
- tapOn:
text: "Wait"
- waitForAnimationToEnd

# Dev client only: clearState wipes the saved Metro server URL, causing the
# development server picker to appear on the next launch.
# Tap the discovered server row (identified by the Mac's LAN IP + port) so the
# saved URL is the routable IP, not localhost (which resolves to the simulator
# itself, not the host machine). METRO_HOST and METRO_PORT are passed by the
# test runner; they default to the values used in the local dev setup.
- runFlow:
when:
platform: iOS
commands:
- runFlow:
when:
visible:
text: "DEVELOPMENT SERVERS"
commands:
- waitForAnimationToEnd
- tapOn:
text: "http://${METRO_HOST}:${METRO_PORT}"
- waitForAnimationToEnd
73 changes: 73 additions & 0 deletions packages/api/.dev.vars.e2e.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# E2E local overrides — copy to .dev.vars.e2e and fill in real secret values.
# The DB, API URL, and Auth URL are pre-configured for local Docker Postgres.
# All other keys should match your main packages/api/.dev.vars.

# ── Database (local Docker, port 5435) ─────────────────────────────────────
NEON_DATABASE_URL=postgres://e2e_user:e2e_pass@localhost:5435/packrat_e2e
NEON_DATABASE_URL_READONLY=postgres://e2e_user:e2e_pass@localhost:5435/packrat_e2e

# ── API & Auth URLs (wrangler dev on localhost) ─────────────────────────────
EXPO_PUBLIC_API_URL=http://localhost:8787
BETTER_AUTH_URL=http://localhost:8787
BETTER_AUTH_SECRET=dev-better-auth-secret-32-characters-long-minimum

# ── E2E credentials (set these to match the seeded test user) ───────────────
E2E_TEST_EMAIL=e2e@packrattest.local
E2E_TEST_PASSWORD=E2eTestPass123!

# ── JWT ─────────────────────────────────────────────────────────────────────
JWT_SECRET=<your-jwt-secret>

# ── AI ──────────────────────────────────────────────────────────────────────
OPENAI_API_KEY=<your-openai-key>
GOOGLE_GENERATIVE_AI_API_KEY=<your-google-ai-key>
PERPLEXITY_API_KEY=<your-perplexity-key>
CLOUDFLARE_AI_GATEWAY_ID=ai-chat-gateway
AI_PROVIDER=openai

# ── Email ────────────────────────────────────────────────────────────────────
EMAIL_PROVIDER=resend
RESEND_API_KEY=<your-resend-key>
EMAIL_FROM=no-reply@transactional.packratai.com

# ── Password reset ───────────────────────────────────────────────────────────
PASSWORD_RESET_SECRET=<your-password-reset-secret>

# ── Weather ──────────────────────────────────────────────────────────────────
WEATHER_API_KEY=<your-weather-api-key>
OPENWEATHER_KEY=<your-openweather-key>

# ── Cloudflare R2 Storage ────────────────────────────────────────────────────
CLOUDFLARE_ACCOUNT_ID=<your-cf-account-id>
R2_ACCESS_KEY_ID=<your-r2-access-key>
R2_SECRET_ACCESS_KEY=<your-r2-secret-key>
PACKRAT_BUCKET_R2_BUCKET_NAME=packrat-bucket-preview
PACKRAT_SCRAPY_BUCKET_R2_BUCKET_NAME=packrat-scrapy-bucket
PACKRAT_GUIDES_BUCKET_R2_BUCKET_NAME=packrat-guides
EXPO_PUBLIC_R2_PUBLIC_URL=https://pub-c3852b07b730407889986338ca3ef0e5.r2.dev
R2_PUBLIC_URL=https://pub-c3852b07b730407889986338ca3ef0e5.r2.dev

# ── Misc ─────────────────────────────────────────────────────────────────────
PACKRAT_API_KEY=secret
ADMIN_USERNAME=admin
ADMIN_PASSWORD=gobuffs
PACKRAT_GUIDES_RAG_NAME=packrat-guides-rag
PACKRAT_GUIDES_BASE_URL=https://guides.packratai.com/

# ── Google OAuth ─────────────────────────────────────────────────────────────
EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=<your-google-ios-client-id>
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=<your-google-web-client-id>
GOOGLE_CLIENT_ID=<your-google-client-id>
GOOGLE_CLIENT_SECRET=<your-google-client-secret>

# ── Maps ─────────────────────────────────────────────────────────────────────
EXPO_PUBLIC_GOOGLE_MAPS_API_KEY=<your-google-maps-key>

# ── Sentry ───────────────────────────────────────────────────────────────────
SENTRY_DSN=<your-sentry-dsn>

# ── Apple Sign In ─────────────────────────────────────────────────────────────
APPLE_CLIENT_ID=<your-apple-client-id>
APPLE_PRIVATE_KEY=<your-apple-private-key>
APPLE_KEY_ID=<your-apple-key-id>
APPLE_TEAM_ID=<your-apple-team-id>
1 change: 1 addition & 0 deletions packages/api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ node_modules/
.wrangler/

.dev.vars
.dev.vars.e2e

# yarn/pnp files - using bun in prod
.pnp.cjs
Expand Down
Loading
Loading