Skip to content

Commit 52e0bc1

Browse files
Merge branch 'main' into 476-extend-assignment-indicator-to-reflect-current-stage-assignee
2 parents 6af2f5d + 5ae02e1 commit 52e0bc1

65 files changed

Lines changed: 2126 additions & 65 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/mcp.maestro.example.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"mcpServers": {
3+
"maestro": {
4+
"command": "${workspaceFolder}/scripts/maestro-mcp.sh",
5+
"args": []
6+
}
7+
}
8+
}

.env.maestro.example

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copy to `.env.maestro` (gitignored via `.env.*`). Never commit real credentials.
2+
#
3+
# Dedicated Maestro translator on the Fluent API (see epic #488 / #491).
4+
# Account must have ≥1 chapter assignment for nav/record/sync smokes.
5+
# `scripts/maestro-test.sh` loads this file literally (no Bash expansion) when present.
6+
# Or: `maestro test -e MAESTRO_EMAIL=... -e MAESTRO_PASSWORD=... .maestro/...`
7+
8+
MAESTRO_EMAIL=
9+
MAESTRO_PASSWORD=
10+
# Second pair for multi-account isolation (#495 / npm run maestro:test:multi-account).
11+
# Each account needs ≥1 chapter assignment; prefer distinct My Work labels (C1–C3).
12+
MAESTRO_EMAIL_2=
13+
MAESTRO_PASSWORD_2=
14+
15+
# API the Debug APK / Metro session should hit (must match your .env / Metro env).
16+
EXPO_PUBLIC_API_BASE_URL=https://dev.api.fluent.bible
17+
18+
# Local Debug + Metro only — suppresses Expo Dev Menu so it does not steal taps.
19+
# Do NOT set on production / preview / nightly EAS profiles.
20+
# EXPO_PUBLIC_E2E_MODE=1

.github/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Workflows for Fluent Mobile (**Android-only**).
1616
| `preview-build.yml` | PR label `preview-build` | Optional isolated Android preview APK (**PR comment only** — debug) |
1717
| `qa-handoff.yml` | PR merged | Needs QA? Yes → issue handoff + assign Roslin + Project 4 `In QA` |
1818
| `nightly-preview.yml` | cron 23:17 PT (APK) + 09:07 PT (Slack) + `workflow_dispatch` | Nightly **binary-only** Android internal APK (dev API); APK cron identity trusted when GitHub delays; Slack held to 09:00–16:00 PT |
19+
| `maestro-android.yml` | `workflow_dispatch` only | **Informational** Maestro E2E (release APK + emulator). Not a required check. See [docs/guides/maestro.md](../docs/guides/maestro.md) |
1920

2021
## PR template + CODEOWNERS
2122

@@ -69,6 +70,8 @@ Scheduled (and manually dispatchable) workflow [`.github/workflows/nightly-previ
6970
| `EXPO_TOKEN` | EAS CLI auth (required for PR preview + nightly) |
7071
| `PROJECT_BOARD_TOKEN` | Optional PAT for Project 4 Status → `In QA` on merge handoff |
7172
| `SLACK_WEBHOOK_URL` | Incoming webhook for nightly success / failure / skip notices |
73+
| `MAESTRO_EMAIL` / `MAESTRO_PASSWORD` | Optional repo secrets — **required** when dispatching `maestro-android.yml` suite `smokes` |
74+
| `MAESTRO_EMAIL_2` / `MAESTRO_PASSWORD_2` | Optional — **required** for suite `multi-account` only (`harness` needs none) |
7275

7376
Does **not** require the Expo GitHub App — only `EXPO_TOKEN`. Manual run: **Actions → Nightly Preview → Run workflow** (available after this workflow exists on `main`).
7477

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Informational Android Maestro E2E — NOT a required PR / merge gate (#497).
2+
# Manual only (workflow_dispatch). Do not add schedule until the suite is stable.
3+
# Does not replace Needs QA? / @Roslin22 nightly handoff.
4+
name: Maestro Android (informational)
5+
6+
on:
7+
workflow_dispatch:
8+
inputs:
9+
suite:
10+
description: Maestro suite to run
11+
type: choice
12+
required: true
13+
default: harness
14+
options:
15+
- harness
16+
- smokes
17+
- multi-account
18+
19+
concurrency:
20+
group: maestro-android-${{ github.ref }}-${{ inputs.suite }}
21+
cancel-in-progress: true
22+
23+
permissions:
24+
contents: read
25+
26+
env:
27+
EXPO_PUBLIC_API_BASE_URL: https://dev.api.fluent.bible
28+
# Release APK is not __DEV__; E2E_MODE is for local Debug+Metro only.
29+
ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64
30+
MAESTRO_VERSION: '2.10.0'
31+
32+
jobs:
33+
maestro-android:
34+
name: Maestro ${{ inputs.suite }}
35+
runs-on: ubuntu-latest
36+
timeout-minutes: 90
37+
38+
steps:
39+
- name: Checkout
40+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
41+
with:
42+
persist-credentials: false
43+
44+
- name: Require secrets for suite
45+
env:
46+
MAESTRO_EMAIL: ${{ secrets.MAESTRO_EMAIL }}
47+
MAESTRO_PASSWORD: ${{ secrets.MAESTRO_PASSWORD }}
48+
MAESTRO_EMAIL_2: ${{ secrets.MAESTRO_EMAIL_2 }}
49+
MAESTRO_PASSWORD_2: ${{ secrets.MAESTRO_PASSWORD_2 }}
50+
run: |
51+
set -euo pipefail
52+
suite="${{ inputs.suite }}"
53+
missing=()
54+
if [[ "${suite}" == "smokes" || "${suite}" == "multi-account" ]]; then
55+
[[ -n "${MAESTRO_EMAIL:-}" ]] || missing+=("MAESTRO_EMAIL")
56+
[[ -n "${MAESTRO_PASSWORD:-}" ]] || missing+=("MAESTRO_PASSWORD")
57+
fi
58+
if [[ "${suite}" == "multi-account" ]]; then
59+
[[ -n "${MAESTRO_EMAIL_2:-}" ]] || missing+=("MAESTRO_EMAIL_2")
60+
[[ -n "${MAESTRO_PASSWORD_2:-}" ]] || missing+=("MAESTRO_PASSWORD_2")
61+
fi
62+
if ((${#missing[@]} > 0)); then
63+
echo "::error::Missing repository secrets for suite=${suite}: ${missing[*]}"
64+
echo "Configure secrets listed in docs/guides/maestro.md / .github/README.md (#497)."
65+
exit 1
66+
fi
67+
echo "Secrets OK for suite=${suite} (harness needs none)."
68+
69+
- name: Setup Node
70+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
71+
with:
72+
node-version-file: '.nvmrc'
73+
cache: 'npm'
74+
75+
- name: Setup Java 17
76+
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
77+
with:
78+
distribution: temurin
79+
java-version: '17'
80+
81+
- name: Enable KVM
82+
run: |
83+
set -euo pipefail
84+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
85+
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
86+
sudo udevadm control --reload-rules
87+
sudo udevadm trigger --name-match=kvm
88+
89+
- name: Install npm dependencies
90+
run: npm ci
91+
92+
- name: Install Maestro CLI
93+
run: npm run maestro:install
94+
95+
- name: Prebuild Android (CNG)
96+
run: npm run prebuild
97+
env:
98+
CI: 'true'
99+
100+
# Assemble inside emulator-runner so ANDROID_HOME / SDK from the action are available.
101+
# android-emulator-runner invokes script with /usr/bin/sh (dash on ubuntu) — run the
102+
# body via a bash heredoc so `pipefail` and ${HOME} expansion work (#510).
103+
- name: Build APK + run Maestro on emulator
104+
uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # v2.34.0
105+
env:
106+
MAESTRO_EMAIL: ${{ secrets.MAESTRO_EMAIL }}
107+
MAESTRO_PASSWORD: ${{ secrets.MAESTRO_PASSWORD }}
108+
MAESTRO_EMAIL_2: ${{ secrets.MAESTRO_EMAIL_2 }}
109+
MAESTRO_PASSWORD_2: ${{ secrets.MAESTRO_PASSWORD_2 }}
110+
with:
111+
api-level: 34
112+
target: google_apis
113+
arch: x86_64
114+
profile: pixel_6
115+
disable-animations: true
116+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -no-metrics
117+
emulator-boot-timeout: 900
118+
script: |
119+
bash -euo pipefail <<'SCRIPT'
120+
export PATH="${HOME}/.maestro/bin:${PATH}"
121+
chmod +x android/gradlew
122+
(
123+
cd android
124+
./gradlew :app:assembleRelease --no-daemon -PreactNativeArchitectures=x86_64
125+
)
126+
APK="android/app/build/outputs/apk/release/app-release.apk"
127+
test -f "${APK}"
128+
adb install -r "${APK}"
129+
mkdir -p .maestro/test_output
130+
suite="${{ inputs.suite }}"
131+
case "${suite}" in
132+
harness)
133+
npm run maestro:test:harness -- --format junit --output .maestro/test_output/report.xml
134+
;;
135+
smokes)
136+
npm run maestro:test:smokes -- --format junit --output .maestro/test_output/report.xml
137+
;;
138+
multi-account)
139+
npm run maestro:test:multi-account -- --format junit --output .maestro/test_output/report.xml
140+
;;
141+
*)
142+
echo "Unknown suite: ${suite}" >&2
143+
exit 1
144+
;;
145+
esac
146+
adb logcat -d > .maestro/test_output/logcat.txt || true
147+
SCRIPT
148+
149+
- name: Upload Maestro artifacts
150+
if: always()
151+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
152+
with:
153+
name: maestro-${{ inputs.suite }}-${{ github.run_id }}
154+
path: |
155+
.maestro/test_output
156+
android/app/build/outputs/apk/release/app-release.apk
157+
if-no-files-found: warn
158+
retention-days: 14

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ pnpm-lock.yaml
4141
.env
4242
.env.*
4343
!.env.example
44+
!.env.maestro.example
45+
46+
# Maestro E2E (local artifacts; flows under .maestro/ are committed)
47+
.maestro/test_output/
48+
.maestro/**/screenshots/
49+
.maestro/**/*.png
50+
.maestro/**/*.mp4
4451

4552
# Expo CNG — native projects are generated locally and in CI
4653
.expo/

.maestro/config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Android-only Maestro workspace (fluent-mobile). See docs/guides/maestro.md.
2+
appId: com.eten.fluent
3+
flows:
4+
- 'flows/**'
5+
- '!helpers/**'
6+
- '!flows/**/subflows/**'
7+
# Relative to this workspace (.maestro/) — outputs land in .maestro/test_output/
8+
testOutputDir: test_output
9+
# disableAnimations applies on Maestro Cloud only; local emulators ignore it.
10+
platform:
11+
android:
12+
disableAnimations: true
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Launch → login → Home; session restore; logout.
2+
appId: com.eten.fluent
3+
tags:
4+
- smoke
5+
- auth
6+
---
7+
- runFlow: subflows/boot-to-login.yaml
8+
- runFlow: subflows/login.yaml
9+
- runFlow: subflows/wait-for-home.yaml
10+
11+
# Session restore without clearState
12+
- stopApp
13+
- launchApp
14+
- runFlow: ../../helpers/dismiss-expo-dev-menu.yaml
15+
- runFlow: subflows/wait-for-home.yaml
16+
17+
# Logout via Settings
18+
- tapOn:
19+
id: 'home-settings-button'
20+
- tapOn:
21+
id: 'settings-menu-more-settings'
22+
- scrollUntilVisible:
23+
element:
24+
id: 'settings-log-out'
25+
direction: DOWN
26+
timeout: 15000
27+
- tapOn:
28+
id: 'settings-log-out'
29+
# Confirm only when the unsynced-work alert is shown
30+
- runFlow:
31+
when:
32+
visible: 'Unsynced work on device'
33+
commands:
34+
- tapOn: 'Log out'
35+
- extendedWaitUntil:
36+
visible:
37+
id: 'login-email-input'
38+
timeout: 60000
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Edges: forgot-password, Privacy/Terms, mic/notification deny (no crash).
2+
# Reauth forced-session is residual — see docs/guides/maestro.md (no backend hook).
3+
appId: com.eten.fluent
4+
tags:
5+
- smoke
6+
- edges
7+
---
8+
# --- Forgot password happy path ---
9+
- runFlow: subflows/boot-to-login.yaml
10+
- tapOn:
11+
id: 'login-forgot-password-link'
12+
- extendedWaitUntil:
13+
visible:
14+
id: 'forgot-password-email-input'
15+
timeout: 15000
16+
- tapOn:
17+
id: 'forgot-password-email-input'
18+
- eraseText
19+
- inputText: ${MAESTRO_EMAIL}
20+
- hideKeyboard
21+
- tapOn:
22+
id: 'forgot-password-submit-button'
23+
- extendedWaitUntil:
24+
visible:
25+
id: 'forgot-password-sent-view'
26+
timeout: 60000
27+
- tapOn:
28+
id: 'forgot-password-sent-back-link'
29+
- extendedWaitUntil:
30+
visible:
31+
id: 'login-email-input'
32+
timeout: 15000
33+
34+
# --- Legal by id (auth login footer) ---
35+
- tapOn:
36+
id: 'login-privacy-link'
37+
- extendedWaitUntil:
38+
visible:
39+
id: 'privacy-policy-scroll'
40+
timeout: 15000
41+
- back
42+
- extendedWaitUntil:
43+
visible:
44+
id: 'login-email-input'
45+
timeout: 15000
46+
- tapOn:
47+
id: 'login-terms-link'
48+
- extendedWaitUntil:
49+
visible:
50+
id: 'terms-of-use-scroll'
51+
timeout: 15000
52+
- back
53+
- extendedWaitUntil:
54+
visible:
55+
id: 'login-email-input'
56+
timeout: 15000
57+
58+
# --- Mic + notification deny: reach Record without crash ---
59+
- launchApp:
60+
clearState: true
61+
permissions:
62+
microphone: deny
63+
notifications: deny
64+
- runFlow: ../../helpers/dismiss-expo-dev-menu.yaml
65+
- extendedWaitUntil:
66+
visible:
67+
id: 'login-email-input'
68+
timeout: 60000
69+
- runFlow: subflows/login.yaml
70+
- runFlow: subflows/wait-for-home.yaml
71+
- runFlow: subflows/open-first-my-work-chapter.yaml
72+
- tapOn:
73+
id: 'drafting-tab-record'
74+
- assertVisible:
75+
id: 'record-tab'
76+
# Attempt start; system may no-op when denied — assert we did not crash
77+
- tapOn:
78+
id: 'record-start-button'
79+
optional: true
80+
- assertVisible:
81+
id: 'record-tab'
82+
# Forced reauth without a backend/session hook is residual — see docs/guides/maestro.md

0 commit comments

Comments
 (0)