Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
name: E2E Tests

on:
workflow_dispatch:
inputs:
force_rebuild:
description: "Force rebuild (ignore cache)"
type: boolean
default: false

env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}

jobs:
fingerprint:
name: 🔍 Check Fingerprint
runs-on: foam
outputs:
hash: ${{ steps.fingerprint.outputs.hash }}
cache-hit: ${{ steps.cache.outputs.cache-hit }}
steps:
- name: 🏗 Checkout
uses: actions/checkout@v4

- name: 🥟 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: ".bun-version"

- name: 📦 Install dependencies
run: bun install --frozen-lockfile

- name: 🔍 Calculate fingerprint
id: fingerprint
run: |
HASH=$(npx expo-updates fingerprint:generate --platform ios 2>/dev/null | jq -r '.hash' || echo "unknown-$(date +%s)")
echo "hash=$HASH" >> $GITHUB_OUTPUT
echo "📱 iOS Fingerprint: $HASH"

- name: 📂 Check build cache
id: cache
if: ${{ !inputs.force_rebuild }}
uses: actions/cache/restore@v4
with:
path: e2e-build.tar.gz
key: e2e-ios-${{ steps.fingerprint.outputs.hash }}
lookup-only: true

build:
name: 🔨 Build E2E App
needs: fingerprint
if: needs.fingerprint.outputs.cache-hit != 'true' || inputs.force_rebuild
runs-on: foam
steps:
- name: 🏗 Checkout
uses: actions/checkout@v4

- name: 🥟 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: ".bun-version"

- name: 📦 Install dependencies
run: bun install --frozen-lockfile

- name: 🔐 Load secrets
uses: 1password/load-secrets-action@v3
with:
export-env: true
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
GOOGLE_SERVICES_IOS_PROD_BS4: op://ci-cd/foam-staging/GOOGLE_SERVICES_IOS_PROD_BS4

- name: 📄 Decode Google Services file
run: |
echo "$GOOGLE_SERVICES_IOS_PROD_BS4" | base64 -d > GoogleService-Info-prod.plist

- name: Λ Setup EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
packager: bun

- name: 📱 Build E2E app
run: |
eas build --profile e2e --platform ios --local --non-interactive --output=e2e-build.tar.gz
env:
APP_VARIANT: e2e

- name: 💾 Save build to cache
uses: actions/cache/save@v4
with:
path: e2e-build.tar.gz
key: e2e-ios-${{ needs.fingerprint.outputs.hash }}

- name: 📤 Upload build artifact
uses: actions/upload-artifact@v4
with:
name: e2e-build
path: e2e-build.tar.gz
retention-days: 30

test:
name: 🧪 Run E2E Tests
needs: [fingerprint, build]
if: always() && needs.fingerprint.result == 'success'
runs-on: macos-latest
steps:
- name: 🏗 Checkout
uses: actions/checkout@v4

- name: 🥟 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: ".bun-version"

- name: 📦 Install dependencies
run: bun install --frozen-lockfile

- name: 📂 Restore build from cache
if: needs.build.result == 'skipped'
uses: actions/cache/restore@v4
with:
path: e2e-build.tar.gz
key: e2e-ios-${{ needs.fingerprint.outputs.hash }}

- name: 📥 Download build artifact
if: needs.build.result == 'success'
uses: actions/download-artifact@v4
with:
name: e2e-build

- name: 📦 Extract app
run: |
tar -xzf e2e-build.tar.gz
APP_PATH=$(find . -name "*.app" -type d | head -1)
echo "APP_PATH=$APP_PATH" >> $GITHUB_ENV
echo "Found app at: $APP_PATH"

- name: Λ Setup EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
packager: bun

- name: 📦 Push OTA update (JS changes only)
if: needs.build.result == 'skipped'
run: |
echo "🚀 Pushing OTA update for JS changes..."
eas update --channel e2e --message "E2E: ${{ github.event.pull_request.title || github.sha }}" --non-interactive || true
env:
APP_VARIANT: e2e

- name: 🎭 Install Maestro
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> $GITHUB_PATH

- name: 📱 Boot Simulator
run: |
DEVICE_ID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -oE '[0-9A-F-]{36}')
xcrun simctl boot "$DEVICE_ID" || true
sleep 5

- name: 📲 Install app on simulator
run: |
xcrun simctl install booted "$APP_PATH"

- name: 🚀 Start mock server
run: |
bun run e2e:mock-server &
sleep 3

- name: 🧪 Run Maestro tests
run: |
bun run maestro:test

- name: 📤 Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-results
path: maestro-debug-output/
retention-days: 7
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,17 @@ xcuserdata
.history/
.turbo

# Cursor
.cursor/

# test
coverage
.jest-cache

maestro-debug-output/
*.maestro-screenshot.png
build-*.tar.gz

# Bun
bun.lockb

Expand All @@ -95,4 +102,6 @@ FOAM_ANDROID_TESTFLIGHT_KEY.json

.env

profile-*.json
profile-*.json


14 changes: 14 additions & 0 deletions .maestro/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Maestro Configuration for Foam E2E Tests
# https://maestro.mobile.dev/reference/configuration
# https://cloud.mobile.dev/reference/workspace-configuration

flows:
- flows/*

excludeTags:
- util

executionOrder:
continueOnFailure: false
flowsOrder:
- app-launch
10 changes: 0 additions & 10 deletions .maestro/example-flow.yml

This file was deleted.

23 changes: 23 additions & 0 deletions .maestro/flows/app-launch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
appId: foam-tv-e2e
name: "App Launch"
tags:
- smoke
- critical
---

- launchApp:
clearState: true

- runFlow:
when:
visible: "Development Build"
file: ../utils/handle-dev-client.yml

- extendedWaitUntil:
visible: "Top|Following"
timeout: 20000

- assertVisible: "Search"
- assertVisible: "Settings"

- takeScreenshot: "app-launch-complete"
37 changes: 37 additions & 0 deletions .maestro/flows/browse-categories.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
appId: foam-tv-e2e
name: "Browse Categories"
tags:
- browse
- categories
---

- launchApp:
clearState: true

- runFlow:
when:
visible: "Development Build"
file: ../utils/handle-dev-client.yml

- extendedWaitUntil:
visible: "Top|Following"
timeout: 20000

- tapOn:
text: "Top"

- extendedWaitUntil:
visible: "Streams|Categories"
timeout: 10000

- tapOn:
text: "Categories"

- extendedWaitUntil:
visible: ".*Just Chatting.*"
timeout: 10000
optional: true

- scroll

- takeScreenshot: "categories-list"
42 changes: 42 additions & 0 deletions .maestro/flows/browse-top-streams.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
appId: foam-tv-e2e
name: "Browse Top Streams"
tags:
- browse
- streams
---

- launchApp:
clearState: true

- runFlow:
when:
visible: "Development Build"
file: ../utils/handle-dev-client.yml

- extendedWaitUntil:
visible: "Top|Following"
timeout: 20000

- tapOn:
text: "Top"

- extendedWaitUntil:
visible: "Streams|Categories"
timeout: 10000

- tapOn:
text: "Streams"
optional: true

- extendedWaitUntil:
visible: ".*Blueberry42.*"
timeout: 10000
optional: true

- scroll

- assertVisible:
text: ".*"
optional: true

- takeScreenshot: "top-streams-list"
54 changes: 54 additions & 0 deletions .maestro/flows/debug-screen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
appId: foam-tv-e2e
name: "Debug Screen"
tags:
- debug
- devtools
---

- launchApp:
clearState: true

- runFlow:
when:
visible: "Development Build"
file: ../utils/handle-dev-client.yml

- extendedWaitUntil:
visible: "Top|Following"
timeout: 20000

- tapOn:
text: "Settings"

- extendedWaitUntil:
visible: "Settings|Developer|Debug"
timeout: 10000

- tapOn:
text: "Developer"
optional: true

- tapOn:
text: "Debug"
optional: true

- tapOn:
text: "DevTools"
optional: true

- extendedWaitUntil:
visible: "Debug|React Query|Diagnostics|Native HLS"
timeout: 10000
optional: true

- takeScreenshot: "debug-screen"

- assertVisible:
text: "Native HLS"
optional: true

- tapOn:
text: "React Query"
optional: true

- takeScreenshot: "debug-screen-toggled"
Loading
Loading