Skip to content

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

Merged
andrew-bierman merged 14 commits into
developmentfrom
copilot/add-maestro-e2e-tests
Mar 12, 2026
Merged

feat: Maestro E2E test suite POC with GitHub Actions iOS + Android CI#1911
andrew-bierman merged 14 commits into
developmentfrom
copilot/add-maestro-e2e-tests

Conversation

Copy link
Copy Markdown
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 third-round CodeRabbit feedback (timeout 90→120m, logout sync dialog, iOS runtime fallback, hideKeyboard before submissions, login wait, README fixes)
  • Fix CI failure: add missing appId: com.andrewbierman.packrat to config.yaml (was causing Config Field Required: appId error)
  • Fix README.md: Replace invalid accessibilityLabel Maestro selector key with correct guidance (idaccessibilityIdentifier, textaccessibilityLabel)
  • Update login-flow.yaml: Replace text-based final assertions with stable id: "create-pack-button" testID selector
  • Add Android Maestro E2E CI job (android-e2e on ubuntu-latest with KVM + reactivecircus/android-emulator-runner)
  • Create .maestro/config-android.yaml with appId: com.packratai.mobile for Android suite
  • Remove per-flow appId headers so individual flows inherit appId from whichever suite config drives them (iOS or Android)
  • Add android: { buildType: "apk" } to the e2e EAS build profile
  • Extract MAESTRO_VERSION to workflow-level env var shared by both iOS and Android jobs
  • Introduce TestIds enum in apps/expo/lib/testIds.ts; update all components to reference enum values instead of inline testID strings
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 CI E2E workflow 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/testIDs, improved accessibility labels, and an e2e simulator build configuration

@andrew-bierman
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Collaborator

@copilot

Copy link
Copy Markdown
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
Copy Markdown
Collaborator

@copilot build failing

@andrew-bierman
Copy link
Copy Markdown
Collaborator

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

@coderabbitai
Copy link
Copy Markdown
Contributor

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
Copy Markdown
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 thread .maestro/flows/setup/clear-state.yaml Outdated
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 thread .github/workflows/e2e-tests.yml Outdated
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
Collaborator

@copilot

@andrew-bierman
Copy link
Copy Markdown
Collaborator

@copilot let's add android maestro tests also pls

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

Copilot AI commented Mar 12, 2026

@copilot let's add android maestro tests also pls

Done in 517f532. Here's what was added:

  • .maestro/config-android.yaml — new suite config with appId: com.packratai.mobile (the production Android package name, which differs from the iOS bundle ID)
  • All flow files — removed per-flow appId headers so they now inherit the appId from whichever suite config drives them (iOS or Android)
  • apps/expo/eas.json — added "android": { "buildType": "apk" } to the e2e profile so EAS local builds produce an installable APK
  • android-e2e job in e2e-tests.yml — runs on ubuntu-latest, enables KVM for hardware acceleration, builds the APK via eas build --local, starts a Pixel 6 API-34 emulator via reactivecircus/android-emulator-runner@v2.34.0, installs the APK, runs the same Maestro flows against config-android.yaml, and uploads results/failure artifacts

MAESTRO_VERSION is now a single workflow-level env var shared by both the iOS and Android jobs.

Copilot AI changed the title feat: Maestro E2E test suite POC with GitHub Actions iOS CI feat: Maestro E2E test suite POC with GitHub Actions iOS + Android CI Mar 12, 2026
Copy link
Copy Markdown
Collaborator

@andrew-bierman andrew-bierman left a comment

Choose a reason for hiding this comment

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

Lgtm!

@andrew-bierman
Copy link
Copy Markdown
Collaborator

@copilot lets use an enum for test ids

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

Copilot AI commented Mar 12, 2026

@copilot lets use an enum for test ids

Done in c84afd6. Created apps/expo/lib/testIds.ts with a TestIds enum and updated all 6 component files to use it:

export enum TestIds {
  SignInEmailButton = 'sign-in-email-button',
  EmailInput = 'email-input',
  PasswordInput = 'password-input',
  ContinueButton = 'continue-button',
  CreateTripButton = 'create-trip-button',
  SubmitTripButton = 'submit-trip-button',
  CreatePackButton = 'create-pack-button',
  SubmitPackButton = 'submit-pack-button',
}

The string values are unchanged, so the Maestro YAML flows continue to work without modification.

@andrew-bierman andrew-bierman changed the base branch from main to dev March 12, 2026 05:00
@andrew-bierman andrew-bierman changed the base branch from dev to development March 12, 2026 05:01
@andrew-bierman andrew-bierman merged commit 7605423 into development Mar 12, 2026
6 of 8 checks passed
@andrew-bierman andrew-bierman deleted the copilot/add-maestro-e2e-tests branch March 12, 2026 05:01
@coderabbitai coderabbitai Bot mentioned this pull request Mar 16, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Mar 30, 2026
This was referenced Apr 20, 2026
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