Skip to content

feat: Maestro E2E test suite POC with GitHub Actions iOS CI#1911

Open
Copilot wants to merge 9 commits intomainfrom
copilot/add-maestro-e2e-tests
Open

feat: Maestro E2E test suite POC with GitHub Actions iOS CI#1911
Copilot wants to merge 9 commits intomainfrom
copilot/add-maestro-e2e-tests

Conversation

Copy link
Contributor

Copilot AI commented Mar 9, 2026

  • Investigate CI build failure (EAS credential error)
  • Add e2e simulator build profile to eas.json
  • Update e2e-tests.yml to use --profile e2e, extract .tar.gz, validate .app path
  • Address first-round CodeRabbit review feedback
  • Fix CI: pass PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN to EAS local build step
  • Address second-round CodeRabbit review feedback (fork guard, iOS filter, TRIP/PACK_NAME vars, localized labels)
  • Fix iOS E2E CI failure: switch runner macos-14macos-15 (Xcode 16)
  • Add stable testID selectors to auth screens; update login-flow.yaml to use id: selectors
  • Address remaining 13 CodeRabbit inline comments:
    • e2e-tests.yml: Increase timeout-minutes 60 → 90 to avoid spurious cancellations on slow runners
    • logout-flow.yaml: Fix ambiguous sync-conflict condition when.visible: "Log Out"when.visible: "Sync in progress" (matches t('profile.syncInProgress'))
    • README.md: Tighten PR trigger description to accurately reflect the path-filter in the workflow
Original prompt

This section details on the original issue you should resolve

<issue_title>Feature: Robust Maestro E2E Tests for CI/CD</issue_title>
<issue_description>## Feature Request: Robust Maestro E2E Tests for CI/CD

Problem Statement
The PackRat mobile app currently lacks reliable end-to-end testing in CI/CD pipelines. Manual testing is time-consuming and error-prone, while existing automated tests don't cover critical user flows on real devices/simulators.

Proposed Solution
Implement comprehensive Maestro E2E test suite that runs reliably in CI/CD environments (GitHub Actions).

Core Requirements

Test Coverage

  • Onboarding flow - First launch, permissions, initial setup
  • Authentication - Login, logout, session persistence
  • Trip Management - Create trip, edit trip, delete trip, view trip details
  • Pack Management - Create pack, add items, remove items, mark as packed
  • Catalog Browsing - Search items, filter by category, view item details
  • Offline Functionality - Core features work without network
  • Settings - Toggle preferences, update profile

CI/CD Integration

  • GitHub Actions workflow for automated test runs
  • iOS Simulator tests (iPhone 15, iOS 17+)
  • Android Emulator tests (Pixel 7 API 34)
  • Parallel test execution to reduce runtime
  • Artifacts upload - Screenshots/videos on failure
  • Slack/Discord notifications for test failures

Technical Implementation

Maestro Configuration

# .maestro/config.yaml
flows:
  - flows/onboarding/*.yaml
  - flows/auth/*.yaml
  - flows/trips/*.yaml
  - flows/packs/*.yaml
  - flows/catalog/*.yaml
  
executionOrder:
  flowsOrder:
    - flows/setup/clear-state.yaml
    - flows/onboarding/welcome-flow.yaml
    - flows/auth/login-flow.yaml
    # ... etc

Test Environment

  • Mock API server for deterministic responses
  • Test fixtures for trips, packs, catalog items
  • Environment-specific config (staging vs prod)

CI/CD Pipeline

# .github/workflows/e2e-tests.yaml
name: E2E Tests
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
    
jobs:
  ios-e2e:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - name: Setup Maestro
        uses: dalexanco/setup-maestro@v1
      - name: Build iOS
        run: eas build --platform ios --profile preview --non-interactive
      - name: Run E2E Tests
        run: maestro test --format junit flows/
      - name: Upload Results
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: ios-test-results
          path: ~/.maestro/tests/

Acceptance Criteria

  1. All critical user flows have Maestro test coverage
  2. Tests run in CI on every PR and main branch push
  3. Tests complete in under 15 minutes (parallelized)
  4. Failed tests provide clear screenshots and logs
  5. Tests are stable (flakiness < 5%)

Effort Estimate

  • Initial setup: 2-3 days
  • Test writing (per flow): 2-4 hours
  • CI/CD integration: 1-2 days
  • Total: ~1-2 weeks for comprehensive coverage

Priority
High - Improves release confidence and reduces manual QA burden.

References

Comments on the Issue (you are @copilot in this section)

@andrew-bierman @copilot This is a high-priority feature request for comprehensive E2E testing. Please create a POC implementation focusing on:
  1. Basic Maestro setup with 2-3 core flows (login, create trip, add pack item)
  2. GitHub Actions workflow for iOS simulator tests
  3. Test environment setup with mock API

Start with minimal viable tests that can run in CI, then expand coverage.</comment_new>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Summary by CodeRabbit

  • Tests

    • Added E2E test workflow in CI to build an iOS simulator, run Maestro-driven end-to-end tests, and collect results/artifacts
    • Added automated flows for setup/cleanup, login/logout, trip creation, and pack creation
  • Documentation

    • Added a Maestro E2E testing guide with setup, commands, CI notes, conventions, and troubleshooting
  • Chores

    • Added test hooks and improved accessibility labels; added an e2e simulator build configuration

@andrew-bierman
Copy link
Collaborator

@copilot

Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
Copilot AI changed the title [WIP] Add robust Maestro E2E tests for CI/CD feat: Maestro E2E test suite POC with GitHub Actions iOS CI Mar 9, 2026
Copy link
Contributor Author

Copilot AI commented Mar 9, 2026

@copilot

It looks like your comment came through empty — did you have a specific question or request about the E2E test implementation? Happy to help with any follow-up.

@andrew-bierman
Copy link
Collaborator

@copilot

Copy link
Contributor Author

Copilot AI commented Mar 9, 2026

@copilot

This comment also came through empty. If you have a specific question or change request about the E2E test implementation, please share the details and I'll take care of it.

@andrew-bierman
Copy link
Collaborator

@copilot build failing

@andrew-bierman
Copy link
Collaborator

@coderabbitai Please review this PR for code quality, best practices, and potential issues.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# List all new files in the PR
find .maestro .github/workflows -type f 2>/dev/null | sort

echo "--- e2e-tests.yml ---"
cat .github/workflows/e2e-tests.yml 2>/dev/null

echo "--- maestro config ---"
cat .maestro/config.yaml 2>/dev/null

echo "--- clear-state ---"
cat .maestro/flows/setup/clear-state.yaml 2>/dev/null

echo "--- login-flow ---"
cat .maestro/flows/auth/login-flow.yaml 2>/dev/null

echo "--- logout-flow ---"
cat .maestro/flows/auth/logout-flow.yaml 2>/dev/null

echo "--- create-trip-flow ---"
cat .maestro/flows/trips/create-trip-flow.yaml 2>/dev/null

echo "--- create-pack-flow ---"
cat .maestro/flows/packs/create-pack-flow.yaml 2>/dev/null

echo "--- README ---"
cat .maestro/README.md 2>/dev/null

Length of output: 11826


@andrew-bierman: Here is a thorough review of this PR. It's a solid POC foundation, but there are a few issues that will cause the CI run to fail before tests even execute, plus some reliability concerns in the flows themselves.


🔴 Critical — Will Break the CI Run

1. EAS local iOS build does not produce a .app bundle directly

EAS local builds output a compressed archive (.tar.gz or .ipa), not a raw .app directory. The workflow passes --output ./build/PackRat.app, then tries to install that path directly on the simulator — this will fail at the install step.

Fix: Extract the .app from the archive after the build:

- name: Build iOS app for simulator
  run: |
    cd apps/expo
    mkdir -p build
    eas build \
      --platform ios \
      --profile preview \
      --non-interactive \
      --local \
      --output ./build/PackRat.tar.gz
  env:
    EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}

- name: Extract .app bundle
  run: |
    cd apps/expo/build
    tar -xzf PackRat.tar.gz
    APP_PATH=$(find . -name "*.app" -maxdepth 3 | head -1)
    echo "APP_PATH=${{ github.workspace }}/apps/expo/build/${APP_PATH}" >> "$GITHUB_ENV"

Then replace apps/expo/build/PackRat.app with $APP_PATH in the install step.


2. id-based taps in trip/pack flows will silently fail

Both create-trip-flow.yaml and create-pack-flow.yaml fall back to:

- tapOn:
    id: "create-trip-button"   # / "create-pack-button"

The PR description explicitly states "no testID attributes exist in the codebase today". Maestro id: matching maps to testID, so these taps will either error or do nothing — the form will never open.

Fix (short-term): Replace with an accessible text or label tap, or add testID attributes to the relevant buttons in the Expo app. For example:

- runFlow:
    when:
      notVisible:
        text: "New Trip"
    commands:
      - tapOn:
          text: "+"   # header plus button — verify actual accessible label

3. Missing test-results/ directory creation

The Maestro command writes to test-results/maestro-results.xml, but the directory is never created. Maestro will fail to write the output file.

- name: Run Maestro E2E tests
  run: |
    mkdir -p test-results   # <-- add this
    maestro --device "$SIMULATOR_UDID" test \
      --format junit \
      --output test-results/maestro-results.xml \
      .maestro/config.yaml

🟡 High — Reliability / Performance

4. Java 17 setup is unnecessary for iOS simulator builds

Java is not required for EAS local iOS builds targeting a simulator. This step adds ~2 minutes of overhead on every run and can be safely removed.

5. No guard when Python runtime-selection scripts return empty strings

If no iOS 17/18 runtime or no iPhone 15 device type is found, IOS_RUNTIME or DEVICE_TYPE will be an empty string and xcrun simctl create will fail with a confusing error. Add explicit exit guards:

- name: Boot iOS Simulator
  run: |
    IOS_RUNTIME=$(xcrun simctl list runtimes --json | python3 -c "
    import sys, json, sys
    runtimes = json.load(sys.stdin)['runtimes']
    ios18 = [r for r in runtimes if r.get('isAvailable') and 'iOS 18' in r.get('name','')]
    ios17 = [r for r in runtimes if r.get('isAvailable') and 'iOS 17' in r.get('name','')]
    chosen = ios18 or ios17
    if not chosen:
        sys.exit('No available iOS 17/18 runtime found')
    print(chosen[-1]['identifier'])
    ")
    # ... same for DEVICE_TYPE

🟠 Medium — Best Practices

6. Pin bun-version and eas-version

Using latest for both is non-deterministic and can cause unexpected breakage when a new version ships.

- uses: oven-sh/setup-bun@v1
  with:
    bun-version: "1.1.38"   # pin to a known-good version

- uses: expo/expo-github-action@v8
  with:
    eas-version: "14.x"     # pin major or exact version

7. No dependency caching

Every CI run does a full bun install from scratch. Add caching to speed up runs significantly:

- name: Cache bun dependencies
  uses: actions/cache@v4
  with:
    path: ~/.bun/install/cache
    key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
    restore-keys: ${{ runner.os }}-bun-

8. Redundant env vars

TEST_EMAIL and TEST_PASSWORD are already declared as job-level env: variables, so re-passing them in the "Run Maestro E2E tests" step is unnecessary. Similarly, EXPO_TOKEN in the EAS build step is already injected by expo/expo-github-action@v8.

9. 60-minute timeout is tight for EAS local iOS builds

EAS local iOS builds routinely take 40–55 minutes. Combined with simulator boot and test execution time, the timeout-minutes: 60 will likely cause the job to be cancelled before tests run. Consider increasing to 90.


🔵 Low — Minor Polish

10. Redundant command in clear-state.yaml

The standalone clearState is redundant when launchApp: { clearState: true } follows immediately:

# Before
- clearState
- launchApp:
    clearState: true
    stopApp: true

# After — one of these is sufficient
- launchApp:
    clearState: true

11. Logout confirmation dialog race condition

In logout-flow.yaml, after tapping "Sign Out", the runFlow when: visible: text: "Sign Out" conditional could match the original button if it hasn't fully dismissed yet, causing a double-tap. Consider using a more specific dialog confirmation text like "Confirm" or "Yes" if the app uses those labels.

12. No minVersion in config.yaml

Consider declaring a minimum Maestro CLI version to avoid silent compatibility issues:

# .maestro/config.yaml
minVersion: "1.40.0"
flows:
  ...

Summary

Severity Issue
🔴 Critical EAS local build outputs .tar.gz/.ipa, not .app — install step will fail
🔴 Critical id:-based taps in trip/pack flows won't match (no testIDs in codebase)
🔴 Critical test-results/ directory not created before Maestro output
🟡 High Java setup step is unnecessary for iOS builds
🟡 High No guard for empty Python runtime/device selection output
🟠 Medium bun-version: latest and eas-version: latest should be pinned
🟠 Medium No dependency caching
🟠 Medium 60-min timeout too tight for EAS local builds
🔵 Low Redundant clearState in clear-state.yaml
🔵 Low Logout dialog confirmation race condition
🔵 Low Missing minVersion in config.yaml

The three critical items will need to be resolved for the CI pipeline to succeed. Happy to help generate the specific diffs for any of them.

[full_review]


🧠 Learnings used
Learnt from: CR
Repo: PackRat-AI/PackRat PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-01T10:08:42.658Z
Learning: Configure required CI/CD environment variables: PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN for GitHub package access, Cloudflare API tokens for API deployment, and Expo credentials for mobile builds

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a proof-of-concept Maestro E2E suite for the Expo mobile app and wires it into GitHub Actions to run on an iOS simulator, targeting core flows (login → create trip → create pack → logout).

Changes:

  • Introduces a Maestro test suite config plus setup/auth/trips/packs flow YAMLs under .maestro/.
  • Adds documentation for running Maestro locally and required CI secrets.
  • Adds an iOS CI workflow that builds the app, boots a simulator, installs the app, runs Maestro, and uploads artifacts.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
.maestro/config.yaml Defines suite flow discovery and explicit execution order.
.maestro/flows/setup/clear-state.yaml Attempts to reset app state for deterministic runs.
.maestro/flows/auth/login-flow.yaml Automates email/password login and asserts tab visibility.
.maestro/flows/auth/logout-flow.yaml Automates logout from the Profile tab and asserts auth screen.
.maestro/flows/trips/create-trip-flow.yaml Navigates to Trips and creates a trip.
.maestro/flows/packs/create-pack-flow.yaml Navigates to Packs and creates a pack.
.maestro/README.md Documents structure, prerequisites, and CI usage.
.github/workflows/e2e-tests.yml Adds macOS iOS-simulator CI job to build/install/run Maestro and upload results/artifacts.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +4 to +6
- clearState
- launchApp:
clearState: true
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

clear-state.yaml runs both clearState and launchApp with clearState: true, which duplicates the same intent and increases suite time. Consider keeping just the launchApp call with clearState: true (or just clearState) to avoid redundant work.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +21
- scrollUntilVisible:
element:
text: "Sign Out"
direction: DOWN

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

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

logout-flow.yaml is looking for and tapping a "Sign Out" button, but the Profile tab’s logout button text in the app is "Log Out" (t('auth.logOut')). As written, scrollUntilVisible/tapOn will never find the element on iOS, so the flow will fail. Update the flow to target the actual "Log Out" label and handle the post-logout alert (e.g., choose "Stay logged out" / "Sign-in again") before asserting the auth screen.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +28
- runFlow:
when:
notVisible:
text: "New Trip"
commands:
- tapOn:
id: "create-trip-button"

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

This flow falls back to tapOn: { id: "create-trip-button" }, but there is no matching accessibility identifier/testID in the app for the Trips header "+" button (it’s a plain <Pressable> wrapping an icon). On non-empty accounts (server already has trips), the "Create New Trip" empty-state button won’t be visible and this fallback will fail. Either add a stable accessibilityLabel/testID for the header create button in the app code and reference that here, or change the flow strategy to not depend on header UI when the list isn’t empty.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +27
- runFlow:
when:
notVisible:
text: "Create Pack"
commands:
- tapOn:
id: "create-pack-button"

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

This flow falls back to tapOn: { id: "create-pack-button" }, but there is no matching accessibility identifier/testID in the app for the Packs header "+" button (it’s a plain <Pressable> wrapping an icon). If the test account already has packs (server-side), the empty-state "Create New Pack" button won’t appear and this fallback will fail. Either add a stable accessibilityLabel/testID in the app code for the header create button and reference it here, or adjust the flow to a selector that exists regardless of list emptiness.

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +44
# Submit the form
- tapOn:
text: "Create Pack"

- waitForAnimationToEnd
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

On the pack creation screen, the navigation title is also "Create Pack" (from the Expo Router Stack options), so tapOn: { text: "Create Pack" } can match the header title instead of the submit button, making the test flaky. Prefer a more specific selector (e.g., target the submit button via an accessibility id/label) so the tap reliably hits the form submit control.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +75
- name: Build iOS app for simulator
run: |
cd apps/expo
eas build \
--platform ios \
--profile preview \
--non-interactive \
--local \
--output ./build/PackRat.app
env:
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The workflow builds with --profile preview but apps/expo/eas.json does not configure an iOS simulator build (no ios.simulator: true in that profile). If EAS outputs an .ipa (device build), xcrun simctl install will fail because it expects a .app. Consider adding a dedicated EAS build profile for simulator builds and using it here (or adjust the workflow to handle the produced artifact type).

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +109
DEVICE_ID=$(xcrun simctl create "PackRat-E2E" "$DEVICE_TYPE" "$IOS_RUNTIME")
xcrun simctl boot "$DEVICE_ID"
echo "SIMULATOR_UDID=$DEVICE_ID" >> "$GITHUB_ENV"

- name: Install app on simulator
run: |
xcrun simctl install "$SIMULATOR_UDID" apps/expo/build/PackRat.app

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

After xcrun simctl boot, the workflow immediately installs the app. simctl boot is not guaranteed to wait until the simulator is fully booted, which can cause intermittent install / Maestro startup failures. Add an explicit wait step (e.g., xcrun simctl bootstatus "$DEVICE_ID" -b) before installing/running tests.

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +89
# Find the latest available iOS 17+ runtime
IOS_RUNTIME=$(xcrun simctl list runtimes --json \
| python3 -c "
import sys, json
runtimes = json.load(sys.stdin)['runtimes']
ios17 = [r for r in runtimes if r.get('isAvailable') and 'iOS 17' in r.get('name','')]
ios18 = [r for r in runtimes if r.get('isAvailable') and 'iOS 18' in r.get('name','')]
chosen = (ios18 or ios17)
print(chosen[-1]['identifier'] if chosen else '')
")
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

IOS_RUNTIME can end up empty if the runner image doesn’t include an iOS 17/18 runtime matching the name filter. In that case simctl create will fail with a hard-to-debug error. Add a guard that errors with a clear message when IOS_RUNTIME is empty, or broaden the selection logic to fall back to the newest available iOS runtime regardless of major version.

Copilot uses AI. Check for mistakes.
Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 9, 2026

@copilot build failing

Fixed in c095adf.

Root cause: The preview EAS profile uses "distribution": "internal", which requires Ad Hoc iOS signing credentials — unavailable in non-interactive CI mode.

Fix:

  1. Added an e2e build profile to apps/expo/eas.json with "ios": { "simulator": true } — produces a simulator .app with no code signing required.
  2. Updated the workflow to use --profile e2e, extract the resulting .tar.gz archive, validate the .app path, and pass it to xcrun simctl install.

@andrew-bierman
Copy link
Collaborator

@coderabbitai Copilot has pushed updates addressing your previous feedback. Please re-review this PR for any remaining issues or approve if it looks good.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me fetch the current state of all relevant files.


🏁 Script executed:

#!/bin/bash
echo "=== e2e-tests.yml ==="
cat .github/workflows/e2e-tests.yml 2>/dev/null

echo ""
echo "=== eas.json ==="
cat apps/expo/eas.json 2>/dev/null

echo ""
echo "=== config.yaml ==="
cat .maestro/config.yaml 2>/dev/null

echo ""
echo "=== clear-state.yaml ==="
cat .maestro/flows/setup/clear-state.yaml 2>/dev/null

echo ""
echo "=== login-flow.yaml ==="
cat .maestro/flows/auth/login-flow.yaml 2>/dev/null

echo ""
echo "=== logout-flow.yaml ==="
cat .maestro/flows/auth/logout-flow.yaml 2>/dev/null

echo ""
echo "=== create-trip-flow.yaml ==="
cat .maestro/flows/trips/create-trip-flow.yaml 2>/dev/null

echo ""
echo "=== create-pack-flow.yaml ==="
cat .maestro/flows/packs/create-pack-flow.yaml 2>/dev/null

Length of output: 9724


@andrew-bierman: Good progress — the EAS .tar.gz extraction fix is solid and the new e2e simulator profile in eas.json is exactly right. Here's what's been resolved and what still needs attention:


✅ Resolved

# Issue
1 EAS build now outputs .tar.gz, extracted correctly with APP_PATH validation and exit 1 guard
2 eas.json now has an e2e profile with ios.simulator: true — no signing required
3 Trip/Pack flows now prefer text-based taps as the primary path

🔴 Still Critical

1. test-results/ directory never created

The Maestro command still writes to test-results/maestro-results.xml but the directory is never mkdir'd. Maestro will error when attempting to write the output file:

- name: Run Maestro E2E tests
  run: |
    mkdir -p test-results    # <-- add this line
    maestro --device "$SIMULATOR_UDID" test \
      --format junit \
      --output test-results/maestro-results.xml \
      .maestro/config.yaml

2. Fallback id: taps still present in trip/pack flows

create-trip-flow.yaml (and create-pack-flow.yaml) still fall back to tapOn: id: "create-trip-button" / "create-pack-button". Since no testID attributes exist in the app, this branch will silently fail. If the empty-state "Create New Trip" text is not visible (e.g. trips already exist from a prior run), the form will never open. Consider replacing the fallback with a more resilient approach — such as tapping by accessibility label, or a header + icon — or simply remove the fallback and document that the test requires a clean state (which clear-state.yaml is already meant to guarantee):

# create-trip-flow.yaml — remove the unreliable id: fallback
- runFlow:
    when:
      visible:
        text: "Create New Trip"
    commands:
      - tapOn:
          text: "Create New Trip"
# If "Create New Trip" is not visible, the header "+" button is the only other option.
# Without a testID, consider tapOn: { text: "+" } or document that clean state is required.

🟡 Still High

3. No bash-level guard on empty IOS_RUNTIME / DEVICE_TYPE

The Python scripts now print an empty string when no runtime is found, but xcrun simctl create will receive an empty argument and fail with a cryptic error. Add explicit bash guards:

if [ -z "$IOS_RUNTIME" ]; then
  echo "::error::No available iOS 17/18 runtime found"
  exit 1
fi
if [ -z "$DEVICE_TYPE" ]; then
  echo "::error::No iPhone 15 device type found"
  exit 1
fi

4. Java 17 setup is still present

The Setup Java step is still in the workflow. EAS local iOS simulator builds do not require Java. This adds ~2 minutes of overhead on every run with no benefit. It can be safely removed.


🟠 Still Medium

5. APP_PATH stored as a relative path

The extract step stores a relative path from find apps/expo/build/extracted -name "*.app". If the working directory changes between steps (unlikely on GitHub Actions but not guaranteed), xcrun simctl install will fail. Prefer an absolute path:

APP_PATH=$(find "$GITHUB_WORKSPACE/apps/expo/build/extracted" -name "*.app" -maxdepth 3 | head -1)

6. bun-version: latest and eas-version: latest still unpinned

Non-deterministic; a new release can break CI silently. Pin to known-good versions (e.g. bun-version: "1.1.38", eas-version: "14.x").

7. No dependency caching

Every CI run still does a full bun install from scratch — adds 2–4 minutes per run. A actions/cache@v4 on ~/.bun/install/cache keyed on bun.lockb would help significantly.

8. 60-minute timeout still risky

EAS local iOS builds typically take 40–55 minutes. Combined with simulator boot (~2 min), Maestro install, and test execution, the job will likely time out before tests run. Recommend timeout-minutes: 90.


🔵 Still Low

  • Redundant clearState in clear-state.yaml- clearState followed immediately by launchApp: { clearState: true } duplicates the state-clear; one is sufficient.
  • Redundant env vars in the Maestro step — TEST_EMAIL and TEST_PASSWORD are already in job-level env:, re-passing them in the step is unnecessary.
  • Logout confirmation race condition — the when: visible: text: "Sign Out" conditional may re-match the original button before it dismisses. A more specific dialog label (e.g. "Confirm") would be more reliable.
  • Missing minVersion in .maestro/config.yaml — pinning minVersion: "1.40.0" ensures the installed CLI version is compatible.

Summary

Severity Issue Status
🔴 Critical test-results/ dir not created Unresolved
🔴 Critical id: fallback taps still present Unresolved
🟡 High No bash guard on empty runtime/device vars Unresolved
🟡 High Unnecessary Java 17 setup Unresolved
🟠 Medium APP_PATH stored as relative path New
🟠 Medium bun-version/eas-version unpinned Unresolved
🟠 Medium No dependency caching Unresolved
🟠 Medium 60-min timeout too tight Unresolved
✅ Resolved EAS .tar.gz extraction with APP_PATH validation Fixed
✅ Resolved eas.json e2e simulator profile Fixed
✅ Resolved Text-based taps as primary path in trip/pack flows Fixed

The test-results/ directory creation and the runtime selection empty-string guards are the two most likely to cause an immediate CI failure — I'd prioritize those along with removing the Java step. Happy to generate the full diffs for any of these.


🧠 Learnings used
Learnt from: CR
Repo: PackRat-AI/PackRat PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-01T10:08:42.658Z
Learning: Configure required CI/CD environment variables: PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN for GitHub package access, Cloudflare API tokens for API deployment, and Expo credentials for mobile builds

@andrew-bierman
Copy link
Collaborator

@copilot CodeRabbit has reviewed this PR and provided feedback. Please address the review comments and push updates. Once you've made the changes, the monitor will ask CodeRabbit to re-review.

@andrew-bierman
Copy link
Collaborator

@copilot

@andrew-bierman
Copy link
Collaborator

@coderabbitai Copilot has pushed updates. Please re-review.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== e2e-tests.yml ==="
cat .github/workflows/e2e-tests.yml 2>/dev/null

echo ""
echo "=== config.yaml ==="
cat .maestro/config.yaml 2>/dev/null

echo ""
echo "=== logout-flow.yaml ==="
cat .maestro/flows/auth/logout-flow.yaml 2>/dev/null

echo ""
echo "=== create-trip-flow.yaml ==="
cat .maestro/flows/trips/create-trip-flow.yaml 2>/dev/null

echo ""
echo "=== create-pack-flow.yaml ==="
cat .maestro/flows/packs/create-pack-flow.yaml 2>/dev/null

Length of output: 8946


@andrew-bierman: I've reviewed the latest push. The only observable change is that create-trip-flow.yaml and create-pack-flow.yaml have been cleaned up to use only id:-based selectors (removing text fallbacks) — which is correct and clean since the testID attributes are already in the app.

All 8 previously flagged items remain unresolved:


🟡 Still High

1. Java 17 setup is still present

Not needed for EAS local iOS simulator builds. ~2 min of overhead per run with no benefit. Remove the entire step:

# Delete entirely:
- name: Setup Java
  uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: "17"

🟠 Still Medium

2. APP_PATH stored as a relative path

# Current — fragile
APP_PATH=$(find apps/expo/build/extracted -name "*.app" -maxdepth 3 | head -1)

# Fix — absolute, always safe
APP_PATH=$(find "$GITHUB_WORKSPACE/apps/expo/build/extracted" -name "*.app" -maxdepth 3 | head -1)

3–5. Unpinned versions, no caching, tight timeout

# Pin bun (currently: latest)
- uses: oven-sh/setup-bun@v1
  with:
    bun-version: "1.1.38"

# Add bun cache (insert before Install dependencies)
- name: Cache bun dependencies
  uses: actions/cache@v4
  with:
    path: ~/.bun/install/cache
    key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
    restore-keys: ${{ runner.os }}-bun-

# Pin eas (currently: latest)
- uses: expo/expo-github-action@v8
  with:
    eas-version: "14.x"
    token: ${{ secrets.EXPO_TOKEN }}

# Increase timeout (currently: 60)
timeout-minutes: 90

🔵 Still Low

6. Redundant env vars in Maestro step

TEST_EMAIL and TEST_PASSWORD are already declared at the job-level env: — the duplicates in the "Run Maestro E2E tests" step are unnecessary:

- name: Run Maestro E2E tests
  run: |
    mkdir -p test-results
    maestro --device "$SIMULATOR_UDID" test \
      --format junit \
      --output test-results/maestro-results.xml \
      .maestro/config.yaml
  env:
    # Remove TEST_EMAIL and TEST_PASSWORD — they're already set at the job level
    TRIP_NAME: E2E-Trip-${{ github.run_id }}
    PACK_NAME: E2E-Pack-${{ github.run_id }}

7. Logout confirmation race condition

After the first tapOn: text: "Log Out" + waitForAnimationToEnd, the runFlow when: visible: text: "Log Out" conditional may still match the original button before it fully dismisses. Add a waitForAnimationToEnd inside the conditional's commands as a guard:

- runFlow:
    when:
      visible:
        text: "Log Out"
    commands:
      - waitForAnimationToEnd   # ← add this guard
      - tapOn:
          text: "Log Out"

8. Missing minVersion in config.yaml

# Add to the top of .maestro/config.yaml
minVersion: "1.40.0"
flows:
  - flows/setup/*.yaml
  ...

Full Status Table

Severity Issue Status
🟡 High Unnecessary Java 17 setup Unresolved
🟠 Medium APP_PATH stored as relative path Unresolved
🟠 Medium bun-version/eas-version unpinned Unresolved
🟠 Medium No dependency caching Unresolved
🟠 Medium timeout-minutes: 60 too tight for EAS local builds Unresolved
🔵 Low Redundant TEST_EMAIL/TEST_PASSWORD in Maestro step env: Unresolved
🔵 Low Logout dialog confirmation race condition Unresolved
🔵 Low Missing minVersion: "1.40.0" in config.yaml Unresolved
Trip/pack flows simplified to id:-only selectors Improved
mkdir -p test-results added Fixed
DEVICE_TYPE empty-string guard via fallback Fixed
testIDs in app; id: taps valid Fixed
EAS .tar.gz extraction with validation Fixed
eas.json e2e simulator profile Fixed
Bash guard for empty IOS_RUNTIME Fixed
Redundant clearState removed Fixed

There are still no CI-breaking issues — all critical blockers are resolved. The remaining 8 items are reliability and best-practice improvements. If useful, I can provide a single consolidated diff for all of them.

@andrew-bierman
Copy link
Collaborator

@coderabbitai Copilot has pushed updates. Please re-review.

@andrew-bierman
Copy link
Collaborator

@copilot The iOS E2E Tests CI check is failing. Since this PR introduces the Maestro E2E test suite, please investigate and fix the test failures. Also, please address the 19 CodeRabbit review comments on this PR.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== e2e-tests.yml ==="
cat .github/workflows/e2e-tests.yml 2>/dev/null

echo ""
echo "=== config.yaml ==="
cat .maestro/config.yaml 2>/dev/null

echo ""
echo "=== logout-flow.yaml ==="
cat .maestro/flows/auth/logout-flow.yaml 2>/dev/null

echo ""
echo "=== create-trip-flow.yaml ==="
cat .maestro/flows/trips/create-trip-flow.yaml 2>/dev/null

echo ""
echo "=== create-pack-flow.yaml ==="
cat .maestro/flows/packs/create-pack-flow.yaml 2>/dev/null

Length of output: 8946


@andrew-bierman: I've reviewed the latest push. Unfortunately, the files appear identical to the previous iteration — all 8 previously flagged items remain unresolved. Here is the complete current status:


No Changes Detected

Severity Issue Status
🟡 High Unnecessary Java 17 setup (actions/setup-java@v4 still present) Unresolved
🟠 Medium APP_PATH stored as relative path (missing $GITHUB_WORKSPACE prefix) Unresolved
🟠 Medium bun-version: latest / eas-version: latest still unpinned Unresolved
🟠 Medium No actions/cache@v4 for bun dependencies Unresolved
🟠 Medium timeout-minutes: 60 still too tight for EAS local builds Unresolved
🔵 Low TEST_EMAIL / TEST_PASSWORD still duplicated in Maestro step env: Unresolved
🔵 Low Logout when: visible: text: "Log Out" race condition — no guard inside conditional Unresolved
🔵 Low minVersion: "1.40.0" still missing from config.yaml Unresolved
mkdir -p test-results added Fixed
DEVICE_TYPE empty-string guard via fallback Fixed
testIDs in app; id: taps valid Fixed
EAS .tar.gz extraction with validation Fixed
eas.json e2e simulator profile Fixed
Bash guard for empty IOS_RUNTIME Fixed
Redundant clearState removed Fixed

As a convenience, here is the complete consolidated diff for all 8 remaining items — this can be applied in a single commit:

.github/workflows/e2e-tests.yml:

# 1. Increase timeout (job level)
timeout-minutes: 90   # was: 60

# 2. Pin bun version
- uses: oven-sh/setup-bun@v1
  with:
    bun-version: "1.1.38"   # was: latest

# 3. Add bun cache — insert before "Install dependencies"
- name: Cache bun dependencies
  uses: actions/cache@v4
  with:
    path: ~/.bun/install/cache
    key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
    restore-keys: ${{ runner.os }}-bun-

# 4. Pin eas version
- uses: expo/expo-github-action@v8
  with:
    eas-version: "14.x"   # was: latest
    token: ${{ secrets.EXPO_TOKEN }}

# 5. Remove the entire Java setup step (not needed for iOS simulator builds):
# - name: Setup Java
#   uses: actions/setup-java@v4
#   with:
#     distribution: temurin
#     java-version: "17"

# 6. Use absolute APP_PATH in the "Extract iOS simulator build" step:
APP_PATH=$(find "$GITHUB_WORKSPACE/apps/expo/build/extracted" -name "*.app" -maxdepth 3 | head -1)

# 7. Remove redundant TEST_EMAIL/TEST_PASSWORD from Maestro step env:
- name: Run Maestro E2E tests
  run: |
    mkdir -p test-results
    maestro --device "$SIMULATOR_UDID" test \
      --format junit \
      --output test-results/maestro-results.xml \
      .maestro/config.yaml
  env:
    # TEST_EMAIL and TEST_PASSWORD removed — already set at job level
    TRIP_NAME: E2E-Trip-${{ github.run_id }}
    PACK_NAME: E2E-Pack-${{ github.run_id }}

.maestro/config.yaml:

# Add to the top of the file
minVersion: "1.40.0"
flows:
  - flows/setup/*.yaml
  ...

.maestro/flows/auth/logout-flow.yaml:

# Add waitForAnimationToEnd inside the conditional to prevent re-matching the original button:
- runFlow:
    when:
      visible:
        text: "Log Out"
    commands:
      - waitForAnimationToEnd   # ← add this guard
      - tapOn:
          text: "Log Out"

…creens, use stable ID selectors in login-flow

Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
@andrew-bierman
Copy link
Collaborator

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor Author

Copilot AI commented Mar 9, 2026

@copilot The iOS E2E Tests CI check is failing. Since this PR introduces the Maestro E2E test suite, please investigate and fix the test failures. Also, please address the 19 CodeRabbit review comme...

Fixed in ba680e5.

CI failure root cause: The app uses onGeometryChange, a SwiftUI API introduced in Xcode 16. The macos-14 runner ships with Xcode 15, which doesn't include this API. Switched the runner to macos-15 (Xcode 16).

CodeRabbit login-flow feedback addressed:

  • Added testID="sign-in-email-button" to the Sign In link button in auth/index.tsx
  • Added testID="email-input" / testID="password-input" to the TextField components in auth/(login)/index.tsx
  • Added testID="continue-button" to the iOS continue Button in auth/(login)/index.tsx
  • Updated login-flow.yaml to use id: selectors for all auth controls (no more fragile text matching)

The other 9 CodeRabbit threads (fork guard, mkdir -p test-results, iOS runtime filter, README selector guidance, localized accessibility labels) were already addressed in the previous commit a72c8b6.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
.maestro/README.md (1)

35-38: ⚠️ Potential issue | 🟡 Minor

Make the local extraction step runnable as written.

tar -C ./build/extracted will fail if that directory does not already exist, so these commands still won't reproduce CI from a clean checkout.

Suggested doc fix
-   - Extract the archive: `tar -xzf ./build/PackRat-sim.tar.gz -C ./build/extracted`
-   - Install on simulator: `xcrun simctl install booted <path-to-extracted.app>`
+   - Extract the archive: `mkdir -p ./build/extracted && tar -xzf ./build/PackRat-sim.tar.gz -C ./build/extracted`
+   - Install on simulator: `xcrun simctl install booted "$(find ./build/extracted -name "*.app" | head -1)"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.maestro/README.md around lines 35 - 38, The extraction step in the README
will fail on a clean checkout because the target directory may not exist; update
the instructions around the tar extraction (the lines showing `tar -xzf
./build/PackRat-sim.tar.gz -C ./build/extracted` and the preceding build
command) to create the destination directory first (e.g., add a step to run
`mkdir -p ./build/extracted` before the tar extraction) so the `tar -xzf ... -C
./build/extracted` command is runnable as written.
🧹 Nitpick comments (1)
.github/workflows/e2e-tests.yml (1)

40-54: Pin Bun and EAS instead of tracking latest.

Both setup steps currently change behavior whenever a new release lands, which makes this already-expensive CI job harder to reproduce and debug when it flakes.

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

In @.github/workflows/e2e-tests.yml around lines 40 - 54, CI workflow currently
pins actions to floating "latest" for bun-version and eas-version in the "Setup
Bun" and "Setup Expo" steps; replace those floating values with explicit, tested
versions (e.g., set the bun-version input to a specific release tag like "0.8.8"
or whatever you validated and set eas-version to a specific EAS CLI version) and
update the action references if needed (oven-sh/setup-bun@v1 and
expo/expo-github-action@v8) so the job uses fixed, reproducible versions; ensure
to update the workflow commit message and optionally add a short comment
explaining why the pins exist for future updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/e2e-tests.yml:
- Around line 27-28: The workflow's job timeout is too low (timeout-minutes: 60)
given it runs EAS iOS builds, simulator boot, and Maestro; update the
timeout-minutes value in the e2e tests workflow (the timeout-minutes setting
under the job that uses runs-on: macos-15) to a larger value (e.g., 120 or 180)
to provide sufficient headroom and prevent spurious cancellations.

In @.maestro/README.md:
- Around line 64-66: Update the README bullet that currently says "Every pull
request to `main` or `development`" to match the workflow's path filters: change
it to state that pull requests to `main` or `development` will run the job only
when they touch `apps/expo/**` or `.maestro/**` (so the three bullets read
consistently with the `.github/workflows/e2e-tests.yml` filters).

---

Duplicate comments:
In @.maestro/README.md:
- Around line 35-38: The extraction step in the README will fail on a clean
checkout because the target directory may not exist; update the instructions
around the tar extraction (the lines showing `tar -xzf
./build/PackRat-sim.tar.gz -C ./build/extracted` and the preceding build
command) to create the destination directory first (e.g., add a step to run
`mkdir -p ./build/extracted` before the tar extraction) so the `tar -xzf ... -C
./build/extracted` command is runnable as written.

---

Nitpick comments:
In @.github/workflows/e2e-tests.yml:
- Around line 40-54: CI workflow currently pins actions to floating "latest" for
bun-version and eas-version in the "Setup Bun" and "Setup Expo" steps; replace
those floating values with explicit, tested versions (e.g., set the bun-version
input to a specific release tag like "0.8.8" or whatever you validated and set
eas-version to a specific EAS CLI version) and update the action references if
needed (oven-sh/setup-bun@v1 and expo/expo-github-action@v8) so the job uses
fixed, reproducible versions; ensure to update the workflow commit message and
optionally add a short comment explaining why the pins exist for future updates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f24d27a-d68b-4b70-b70d-dbf2f193e2e8

📥 Commits

Reviewing files that changed from the base of the PR and between 66d1ae0 and ba680e5.

📒 Files selected for processing (10)
  • .github/workflows/e2e-tests.yml
  • .maestro/README.md
  • .maestro/flows/auth/login-flow.yaml
  • .maestro/flows/auth/logout-flow.yaml
  • .maestro/flows/packs/create-pack-flow.yaml
  • .maestro/flows/trips/create-trip-flow.yaml
  • apps/expo/app/auth/(login)/index.tsx
  • apps/expo/app/auth/index.tsx
  • apps/expo/features/packs/screens/PackListScreen.tsx
  • apps/expo/features/trips/screens/TripListScreen.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/expo/features/packs/screens/PackListScreen.tsx
  • .maestro/flows/packs/create-pack-flow.yaml
  • apps/expo/features/trips/screens/TripListScreen.tsx
  • .maestro/flows/trips/create-trip-flow.yaml
  • .maestro/flows/auth/logout-flow.yaml
  • .maestro/flows/auth/login-flow.yaml

@andrew-bierman
Copy link
Collaborator

@coderabbitai Copilot has pushed updates. Please re-review.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== e2e-tests.yml ==="
cat .github/workflows/e2e-tests.yml 2>/dev/null

echo ""
echo "=== config.yaml ==="
cat .maestro/config.yaml 2>/dev/null

echo ""
echo "=== logout-flow.yaml ==="
cat .maestro/flows/auth/logout-flow.yaml 2>/dev/null

echo ""
echo "=== create-trip-flow.yaml ==="
cat .maestro/flows/trips/create-trip-flow.yaml 2>/dev/null

echo ""
echo "=== create-pack-flow.yaml ==="
cat .maestro/flows/packs/create-pack-flow.yaml 2>/dev/null

Length of output: 8946


@andrew-bierman: I've reviewed the latest push. The files appear identical to the previous iteration — all 8 previously flagged items remain unresolved. Here's the complete current status:


No Changes Detected

Severity Issue Status
🟡 High Unnecessary Java 17 setup (actions/setup-java@v4 still present) Unresolved
🟠 Medium APP_PATH stored as relative path (missing $GITHUB_WORKSPACE prefix) Unresolved
🟠 Medium bun-version: latest still unpinned Unresolved
🟠 Medium eas-version: latest still unpinned Unresolved
🟠 Medium No actions/cache@v4 for bun dependencies Unresolved
🟠 Medium timeout-minutes: 60 still too tight for EAS local builds Unresolved
🔵 Low TEST_EMAIL / TEST_PASSWORD still duplicated in Maestro step env: Unresolved
🔵 Low Logout runFlow when: visible: text: "Log Out" — no waitForAnimationToEnd guard inside conditional Unresolved
🔵 Low minVersion: "1.40.0" still missing from config.yaml Unresolved
mkdir -p test-results added Fixed
DEVICE_TYPE empty-string guard via fallback Fixed
testIDs in app; id: taps valid Fixed
EAS .tar.gz extraction with validation Fixed
eas.json e2e simulator profile Fixed
Bash guard for empty IOS_RUNTIME Fixed
Redundant clearState removed Fixed

There are still no CI-breaking issues — all critical blockers remain fixed. For Copilot's reference, here is the complete consolidated diff to address all 8 remaining items in one commit:

.github/workflows/e2e-tests.yml:

# 1. Increase timeout
timeout-minutes: 90   # was: 60

# 2. Pin bun version
- uses: oven-sh/setup-bun@v1
  with:
    bun-version: "1.1.38"   # was: latest

# 3. Add bun cache — insert before "Install dependencies"
- name: Cache bun dependencies
  uses: actions/cache@v4
  with:
    path: ~/.bun/install/cache
    key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
    restore-keys: ${{ runner.os }}-bun-

# 4. Pin eas version
- uses: expo/expo-github-action@v8
  with:
    eas-version: "14.x"   # was: latest
    token: ${{ secrets.EXPO_TOKEN }}

# 5. Remove the entire Java setup step:
# - name: Setup Java
#   uses: actions/setup-java@v4
#   with:
#     distribution: temurin
#     java-version: "17"

# 6. Use absolute APP_PATH in the "Extract iOS simulator build" step:
APP_PATH=$(find "$GITHUB_WORKSPACE/apps/expo/build/extracted" -name "*.app" -maxdepth 3 | head -1)

# 7. Remove TEST_EMAIL / TEST_PASSWORD from Maestro step env: (already set at job level)
- name: Run Maestro E2E tests
  run: |
    mkdir -p test-results
    maestro --device "$SIMULATOR_UDID" test \
      --format junit \
      --output test-results/maestro-results.xml \
      .maestro/config.yaml
  env:
    TRIP_NAME: E2E-Trip-${{ github.run_id }}
    PACK_NAME: E2E-Pack-${{ github.run_id }}

.maestro/config.yaml:

minVersion: "1.40.0"   # add this line at the top
flows:
  - flows/setup/*.yaml
  ...

.maestro/flows/auth/logout-flow.yaml:

- runFlow:
    when:
      visible:
        text: "Log Out"
    commands:
      - waitForAnimationToEnd   # add this guard before the tap
      - tapOn:
          text: "Log Out"

@andrew-bierman
Copy link
Collaborator

@copilot CodeRabbit re-review still has 13 inline comments remaining. Please continue addressing the review feedback.

@andrew-bierman
Copy link
Collaborator

👋 @copilot

@andrew-bierman
Copy link
Collaborator

@coderabbitai Copilot has pushed updates. Please re-review.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== e2e-tests.yml ==="
cat .github/workflows/e2e-tests.yml 2>/dev/null

echo ""
echo "=== config.yaml ==="
cat .maestro/config.yaml 2>/dev/null

echo ""
echo "=== logout-flow.yaml ==="
cat .maestro/flows/auth/logout-flow.yaml 2>/dev/null

echo ""
echo "=== create-trip-flow.yaml ==="
cat .maestro/flows/trips/create-trip-flow.yaml 2>/dev/null

echo ""
echo "=== create-pack-flow.yaml ==="
cat .maestro/flows/packs/create-pack-flow.yaml 2>/dev/null

Length of output: 8946


@andrew-bierman: I've reviewed the latest push. The files are identical to the previous iteration — all 8 previously flagged items remain unresolved. Here is the complete current status:


No Changes Detected

Severity Issue Status
🟡 High Unnecessary Java 17 setup (actions/setup-java@v4 still present) Unresolved
🟠 Medium APP_PATH stored as relative path (missing $GITHUB_WORKSPACE prefix) Unresolved
🟠 Medium bun-version: latest unpinned Unresolved
🟠 Medium eas-version: latest unpinned Unresolved
🟠 Medium No actions/cache@v4 for bun dependencies Unresolved
🟠 Medium timeout-minutes: 60 too tight for EAS local iOS builds Unresolved
🔵 Low TEST_EMAIL / TEST_PASSWORD duplicated in Maestro step env: Unresolved
🔵 Low runFlow when: visible: text: "Log Out" — no waitForAnimationToEnd inside conditional Unresolved
🔵 Low minVersion missing from config.yaml Unresolved
mkdir -p test-results added Fixed
DEVICE_TYPE empty-string guard via fallback Fixed
testIDs in app; id: taps valid Fixed
EAS .tar.gz extraction with validation Fixed
eas.json e2e simulator profile Fixed
Bash guard for empty IOS_RUNTIME Fixed
Redundant clearState removed Fixed

There are still no CI-breaking issues — all critical blockers remain fixed. Here is the complete consolidated diff for all 8 remaining items, ready to apply in one commit:

.github/workflows/e2e-tests.yml:

# 1. Increase timeout
timeout-minutes: 90   # was: 60

# 2. Pin bun version
- uses: oven-sh/setup-bun@v1
  with:
    bun-version: "1.1.38"   # was: latest

# 3. Add bun cache — insert before "Install dependencies"
- name: Cache bun dependencies
  uses: actions/cache@v4
  with:
    path: ~/.bun/install/cache
    key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
    restore-keys: ${{ runner.os }}-bun-

# 4. Pin eas version
- uses: expo/expo-github-action@v8
  with:
    eas-version: "14.x"   # was: latest
    token: ${{ secrets.EXPO_TOKEN }}

# 5. Remove the entire Java setup step (not needed for iOS simulator builds):
# - name: Setup Java
#   uses: actions/setup-java@v4
#   with:
#     distribution: temurin
#     java-version: "17"

# 6. Use absolute APP_PATH in the "Extract iOS simulator build" step:
APP_PATH=$(find "$GITHUB_WORKSPACE/apps/expo/build/extracted" -name "*.app" -maxdepth 3 | head -1)

# 7. Remove TEST_EMAIL / TEST_PASSWORD from Maestro step env (already set at job level):
- name: Run Maestro E2E tests
  run: |
    mkdir -p test-results
    maestro --device "$SIMULATOR_UDID" test \
      --format junit \
      --output test-results/maestro-results.xml \
      .maestro/config.yaml
  env:
    # TEST_EMAIL and TEST_PASSWORD removed — already set at job level
    TRIP_NAME: E2E-Trip-${{ github.run_id }}
    PACK_NAME: E2E-Pack-${{ github.run_id }}

.maestro/config.yaml:

minVersion: "1.40.0"   # add this at the top
flows:
  - flows/setup/*.yaml
  ...

.maestro/flows/auth/logout-flow.yaml:

- runFlow:
    when:
      visible:
        text: "Log Out"
    commands:
      - waitForAnimationToEnd   # add this guard before the tap
      - tapOn:
          text: "Log Out"

… sync condition, README trigger wording

Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
@andrew-bierman
Copy link
Collaborator

@copilot There are still 13 unresolved CodeRabbit inline comments. Please address the remaining review feedback.

@andrew-bierman
Copy link
Collaborator

@coderabbitai Copilot has pushed updates. Please re-review.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== e2e-tests.yml ==="
cat .github/workflows/e2e-tests.yml 2>/dev/null

echo ""
echo "=== config.yaml ==="
cat .maestro/config.yaml 2>/dev/null

echo ""
echo "=== logout-flow.yaml ==="
cat .maestro/flows/auth/logout-flow.yaml 2>/dev/null

echo ""
echo "=== create-trip-flow.yaml ==="
cat .maestro/flows/trips/create-trip-flow.yaml 2>/dev/null

echo ""
echo "=== create-pack-flow.yaml ==="
cat .maestro/flows/packs/create-pack-flow.yaml 2>/dev/null

echo ""
echo "=== login-flow.yaml ==="
cat .maestro/flows/auth/login-flow.yaml 2>/dev/null

echo ""
echo "=== clear-state.yaml ==="
cat .maestro/flows/setup/clear-state.yaml 2>/dev/null

echo ""
echo "=== eas.json ==="
cat apps/expo/eas.json 2>/dev/null

Length of output: 10653


@andrew-bierman: Good progress — two more items are now resolved. Here's the updated status:


✅ Newly Resolved

# Issue
1 timeout-minutes increased to 90 — EAS local builds no longer risk timing out
2 Logout race condition fixed — runFlow when: now triggers on "Sync in progress" instead of "Log Out", eliminating the risk of re-matching the original button

🟡 Still High

1. Java 17 setup is still present

The Setup Java step remains and is not needed for EAS local iOS simulator builds. It adds ~2 min of overhead per run with no benefit:

# Remove this entire step:
- name: Setup Java
  uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: "17"

🟠 Still Medium

2. APP_PATH stored as a relative path

# Current (relative — fragile if working directory shifts between steps)
APP_PATH=$(find apps/expo/build/extracted -name "*.app" -maxdepth 3 | head -1)

# Fix (absolute — always safe)
APP_PATH=$(find "$GITHUB_WORKSPACE/apps/expo/build/extracted" -name "*.app" -maxdepth 3 | head -1)

3–5. Unpinned versions and no caching

All three remain unchanged:

# Pin bun version
- uses: oven-sh/setup-bun@v1
  with:
    bun-version: "1.1.38"   # was: latest

# Add bun cache — insert before "Install dependencies"
- name: Cache bun dependencies
  uses: actions/cache@v4
  with:
    path: ~/.bun/install/cache
    key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
    restore-keys: ${{ runner.os }}-bun-

# Pin eas version
- uses: expo/expo-github-action@v8
  with:
    eas-version: "14.x"   # was: latest
    token: ${{ secrets.EXPO_TOKEN }}

🔵 Still Low

6. Redundant env vars in Maestro step

TEST_EMAIL and TEST_PASSWORD are already declared at the job-level env: — the duplicates in the "Run Maestro E2E tests" step are unnecessary:

- name: Run Maestro E2E tests
  run: |
    mkdir -p test-results
    maestro --device "$SIMULATOR_UDID" test \
      --format junit \
      --output test-results/maestro-results.xml \
      .maestro/config.yaml
  env:
    # Remove TEST_EMAIL and TEST_PASSWORD — already set at job level
    TRIP_NAME: E2E-Trip-${{ github.run_id }}
    PACK_NAME: E2E-Pack-${{ github.run_id }}

7. Missing minVersion in config.yaml

# Add to the top of .maestro/config.yaml
minVersion: "1.40.0"
flows:
  - flows/setup/*.yaml
  ...

Full Status Table

Severity Issue Status
🟡 High Unnecessary Java 17 setup Unresolved
🟠 Medium APP_PATH stored as relative path Unresolved
🟠 Medium bun-version: latest unpinned Unresolved
🟠 Medium eas-version: latest unpinned Unresolved
🟠 Medium No actions/cache@v4 for bun dependencies Unresolved
🔵 Low Redundant TEST_EMAIL/TEST_PASSWORD in Maestro step env: Unresolved
🔵 Low Missing minVersion: "1.40.0" in config.yaml Unresolved
timeout-minutes increased to 90 Fixed
Logout race condition resolved ("Sync in progress" trigger) Fixed
mkdir -p test-results added Fixed
DEVICE_TYPE empty-string guard via fallback Fixed
testIDs in app; id: taps valid Fixed
EAS .tar.gz extraction with validation Fixed
eas.json e2e simulator profile Fixed
Bash guard for empty IOS_RUNTIME Fixed
Redundant clearState removed Fixed

There are still no CI-breaking issues — all critical blockers remain fixed. The 7 remaining items are reliability and best-practice improvements. The Java step removal is the highest-impact remaining change.

@andrew-bierman
Copy link
Collaborator

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
.maestro/flows/auth/logout-flow.yaml (1)

25-31: ⚠️ Potential issue | 🟠 Major

Scope the confirmation tap to the dialog action.

Line 30 still has the same ambiguity: the when.visible.text: "Sync in progress" guard controls when this block runs, but tapOn: { text: "Log Out" } can still match the underlying settings row instead of the dialog button. Please switch this step to a dialog-specific selector or a dedicated test/accessibility id for the confirm action.

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

In @.maestro/flows/auth/logout-flow.yaml around lines 25 - 31, The tap step in
runFlow is ambiguous because when.visible.text: "Sync in progress" only gates
execution but tapOn: { text: "Log Out" } can match the underlying settings row;
update the logout confirmation step in logout-flow.yaml to target the dialog
action specifically by replacing the generic tapOn text selector with a
dialog-scoped selector or a dedicated accessibility id (e.g., use the dialog
button selector or tapOn with the confirm button's accessibilityId like
"logout-confirm") so the tap targets the confirmation button in the dialog
rather than the settings row.
.maestro/README.md (1)

35-38: ⚠️ Potential issue | 🟡 Minor

Create the extraction directory and show the resolved .app path.

Line 37 will fail on a clean machine because tar -C ./build/extracted expects that directory to already exist. The install step is also still a placeholder, so local setup does not quite mirror the CI flow yet.

Suggested doc fix
-   - Extract the archive: `tar -xzf ./build/PackRat-sim.tar.gz -C ./build/extracted`
-   - Install on simulator: `xcrun simctl install booted <path-to-extracted.app>`
+   - Extract the archive: `mkdir -p ./build/extracted && tar -xzf ./build/PackRat-sim.tar.gz -C ./build/extracted`
+   - Install on simulator: `xcrun simctl install booted "$(find ./build/extracted -name "*.app" -type d | head -1)"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.maestro/README.md around lines 35 - 38, The README's extraction and install
steps assume ./build/extracted exists and use a placeholder path; before running
tar -xzf ./build/PackRat-sim.tar.gz -C ./build/extracted add a mkdir -p
./build/extracted to create the directory, and replace the placeholder
<path-to-extracted.app> in the xcrun simctl install command by resolving and
echoing the actual .app path (e.g., find ./build/extracted -name "*.app" -print
-quit) so the documented install step uses the real extracted .app file instead
of a placeholder.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.maestro/README.md:
- Around line 7-20: The fenced code block showing the directory tree (the block
that begins with ``` and contains the .maestro/ tree) needs a language tag to
satisfy markdownlint; update that opening fence to include a tag such as "text"
or "plaintext" (e.g., change ``` to ```text) so the README's tree block is
lint-clean while preserving the existing content.

---

Duplicate comments:
In @.maestro/flows/auth/logout-flow.yaml:
- Around line 25-31: The tap step in runFlow is ambiguous because
when.visible.text: "Sync in progress" only gates execution but tapOn: { text:
"Log Out" } can match the underlying settings row; update the logout
confirmation step in logout-flow.yaml to target the dialog action specifically
by replacing the generic tapOn text selector with a dialog-scoped selector or a
dedicated accessibility id (e.g., use the dialog button selector or tapOn with
the confirm button's accessibilityId like "logout-confirm") so the tap targets
the confirmation button in the dialog rather than the settings row.

In @.maestro/README.md:
- Around line 35-38: The README's extraction and install steps assume
./build/extracted exists and use a placeholder path; before running tar -xzf
./build/PackRat-sim.tar.gz -C ./build/extracted add a mkdir -p ./build/extracted
to create the directory, and replace the placeholder <path-to-extracted.app> in
the xcrun simctl install command by resolving and echoing the actual .app path
(e.g., find ./build/extracted -name "*.app" -print -quit) so the documented
install step uses the real extracted .app file instead of a placeholder.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 42828add-8b06-4580-a8c0-6a76a592b876

📥 Commits

Reviewing files that changed from the base of the PR and between ba680e5 and 1f3a067.

📒 Files selected for processing (3)
  • .github/workflows/e2e-tests.yml
  • .maestro/README.md
  • .maestro/flows/auth/logout-flow.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/e2e-tests.yml

Comment on lines +7 to +20
```
.maestro/
├── config.yaml # Maestro suite configuration & flow order
└── flows/
├── setup/
│ └── clear-state.yaml # Clears app state before test suite
├── auth/
│ ├── login-flow.yaml # Sign in with email and password
│ └── logout-flow.yaml # Sign out from the app
├── trips/
│ └── create-trip-flow.yaml # Create a new trip
└── packs/
└── create-pack-flow.yaml # Create a new pack
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a language tag to the fenced structure block.

markdownlint is flagging this block already. Mark it as text/plaintext so the README stays lint-clean.

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

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

In @.maestro/README.md around lines 7 - 20, The fenced code block showing the
directory tree (the block that begins with ``` and contains the .maestro/ tree)
needs a language tag to satisfy markdownlint; update that opening fence to
include a tag such as "text" or "plaintext" (e.g., change ``` to ```text) so the
README's tree block is lint-clean while preserving the existing content.

@andrew-bierman
Copy link
Collaborator

@coderabbitai Copilot has pushed updates. Please re-review.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== e2e-tests.yml ==="
cat .github/workflows/e2e-tests.yml 2>/dev/null

echo ""
echo "=== config.yaml ==="
cat .maestro/config.yaml 2>/dev/null

echo ""
echo "=== logout-flow.yaml ==="
cat .maestro/flows/auth/logout-flow.yaml 2>/dev/null

echo ""
echo "=== create-trip-flow.yaml ==="
cat .maestro/flows/trips/create-trip-flow.yaml 2>/dev/null

echo ""
echo "=== create-pack-flow.yaml ==="
cat .maestro/flows/packs/create-pack-flow.yaml 2>/dev/null

echo ""
echo "=== login-flow.yaml ==="
cat .maestro/flows/auth/login-flow.yaml 2>/dev/null

echo ""
echo "=== clear-state.yaml ==="
cat .maestro/flows/setup/clear-state.yaml 2>/dev/null

echo ""
echo "=== eas.json ==="
cat apps/expo/eas.json 2>/dev/null

Length of output: 10653


@andrew-bierman: I've reviewed the latest push. The files are identical to the previous iteration — all 7 previously flagged items remain unresolved. Here is the complete current status:


No Changes Detected

Severity Issue Status
🟡 High Unnecessary Java 17 setup (actions/setup-java@v4 still present) Unresolved
🟠 Medium APP_PATH stored as relative path (missing $GITHUB_WORKSPACE prefix) Unresolved
🟠 Medium bun-version: latest unpinned Unresolved
🟠 Medium eas-version: latest unpinned Unresolved
🟠 Medium No actions/cache@v4 for bun dependencies Unresolved
🔵 Low TEST_EMAIL / TEST_PASSWORD duplicated in Maestro step env: Unresolved
🔵 Low minVersion missing from config.yaml Unresolved
timeout-minutes increased to 90 Fixed
Logout race condition fixed ("Sync in progress" trigger) Fixed
mkdir -p test-results added Fixed
DEVICE_TYPE empty-string guard via fallback Fixed
testIDs in app; id: taps valid Fixed
EAS .tar.gz extraction with validation Fixed
eas.json e2e simulator profile Fixed
Bash guard for empty IOS_RUNTIME Fixed
Redundant clearState removed Fixed

There are still no CI-breaking issues — all critical blockers remain fixed. Here is the complete consolidated diff for all 7 remaining items, ready to apply in one commit:

.github/workflows/e2e-tests.yml:

# 1. Pin bun version
- uses: oven-sh/setup-bun@v1
  with:
    bun-version: "1.1.38"   # was: latest

# 2. Add bun cache — insert before "Install dependencies"
- name: Cache bun dependencies
  uses: actions/cache@v4
  with:
    path: ~/.bun/install/cache
    key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
    restore-keys: ${{ runner.os }}-bun-

# 3. Pin eas version
- uses: expo/expo-github-action@v8
  with:
    eas-version: "14.x"   # was: latest
    token: ${{ secrets.EXPO_TOKEN }}

# 4. Remove the entire Java setup step (not needed for iOS simulator builds):
# - name: Setup Java
#   uses: actions/setup-java@v4
#   with:
#     distribution: temurin
#     java-version: "17"

# 5. Use absolute APP_PATH in the "Extract iOS simulator build" step:
APP_PATH=$(find "$GITHUB_WORKSPACE/apps/expo/build/extracted" -name "*.app" -maxdepth 3 | head -1)

# 6. Remove TEST_EMAIL / TEST_PASSWORD from Maestro step env (already set at job level):
- name: Run Maestro E2E tests
  run: |
    mkdir -p test-results
    maestro --device "$SIMULATOR_UDID" test \
      --format junit \
      --output test-results/maestro-results.xml \
      .maestro/config.yaml
  env:
    # TEST_EMAIL and TEST_PASSWORD removed — already set at job level
    TRIP_NAME: E2E-Trip-${{ github.run_id }}
    PACK_NAME: E2E-Pack-${{ github.run_id }}

.maestro/config.yaml:

# 7. Add minVersion at the top of the file
minVersion: "1.40.0"
flows:
  - flows/setup/*.yaml
  - flows/auth/*.yaml
  - flows/trips/*.yaml
  - flows/packs/*.yaml

@andrew-bierman
Copy link
Collaborator

@copilot CodeRabbit re-reviewed and found 14 inline comments remaining. Please address all outstanding review feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Robust Maestro E2E Tests for CI/CD

3 participants