Maestro: 10 golden-path flows for critical user journeys
Cross-platform YAML flows (iOS + Android share the AccessibilityIds test-tag namespace). Covers login, register, residence/task CRUD, completion, join, document upload, theme, deeplink, widget. Run: maestro test .maestro/flows/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
11
.maestro/config.yaml
Normal file
11
.maestro/config.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
flows:
|
||||||
|
- flows/01-login.yaml
|
||||||
|
- flows/02-register.yaml
|
||||||
|
- flows/03-create-residence.yaml
|
||||||
|
- flows/04-create-task.yaml
|
||||||
|
- flows/05-complete-task.yaml
|
||||||
|
- flows/06-join-residence.yaml
|
||||||
|
- flows/07-upload-document.yaml
|
||||||
|
- flows/08-theme-switch.yaml
|
||||||
|
- flows/09-notification-deeplink.yaml
|
||||||
|
- flows/10-widget-complete.yaml
|
||||||
26
.maestro/flows/01-login.yaml
Normal file
26
.maestro/flows/01-login.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Golden path: existing user signs in with email+password and lands on tabs.
|
||||||
|
# Cross-platform — uses AccessibilityIds test tags shared with iOS.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Login happy path
|
||||||
|
tags:
|
||||||
|
- smoke
|
||||||
|
- auth
|
||||||
|
---
|
||||||
|
- launchApp:
|
||||||
|
clearState: true
|
||||||
|
- tapOn:
|
||||||
|
id: "Login.UsernameField"
|
||||||
|
- inputText: "testuser@example.com"
|
||||||
|
- tapOn:
|
||||||
|
id: "Login.PasswordField"
|
||||||
|
- inputText: "TestPassword123!"
|
||||||
|
- tapOn:
|
||||||
|
id: "Login.LoginButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "TabBar.Tasks"
|
||||||
|
timeout: 15000
|
||||||
|
- assertVisible:
|
||||||
|
id: "TabBar.Tasks"
|
||||||
|
- assertVisible:
|
||||||
|
id: "TabBar.Residences"
|
||||||
31
.maestro/flows/02-register.yaml
Normal file
31
.maestro/flows/02-register.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Golden path: new user registers and is routed to the verify-email stub.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Register happy path
|
||||||
|
tags:
|
||||||
|
- smoke
|
||||||
|
- auth
|
||||||
|
---
|
||||||
|
- launchApp:
|
||||||
|
clearState: true
|
||||||
|
- tapOn:
|
||||||
|
id: "Login.SignUpButton"
|
||||||
|
- tapOn:
|
||||||
|
id: "Register.UsernameField"
|
||||||
|
- inputText: "newuser_maestro"
|
||||||
|
- tapOn:
|
||||||
|
id: "Register.EmailField"
|
||||||
|
- inputText: "new+maestro@example.com"
|
||||||
|
- tapOn:
|
||||||
|
id: "Register.PasswordField"
|
||||||
|
- inputText: "NewPassword123!"
|
||||||
|
- tapOn:
|
||||||
|
id: "Register.ConfirmPasswordField"
|
||||||
|
- inputText: "NewPassword123!"
|
||||||
|
- tapOn:
|
||||||
|
id: "Register.RegisterButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Verification.CodeField"
|
||||||
|
timeout: 15000
|
||||||
|
- assertVisible:
|
||||||
|
id: "Verification.VerifyButton"
|
||||||
39
.maestro/flows/03-create-residence.yaml
Normal file
39
.maestro/flows/03-create-residence.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Golden path: login → Residences tab → Add → fill form → Save.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Create residence
|
||||||
|
tags:
|
||||||
|
- smoke
|
||||||
|
- residence
|
||||||
|
---
|
||||||
|
- runFlow: 01-login.yaml
|
||||||
|
- tapOn:
|
||||||
|
id: "TabBar.Residences"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Residence.AddButton"
|
||||||
|
timeout: 10000
|
||||||
|
- tapOn:
|
||||||
|
id: "Residence.AddButton"
|
||||||
|
- tapOn:
|
||||||
|
id: "ResidenceForm.NameField"
|
||||||
|
- inputText: "Maestro Test Residence"
|
||||||
|
- tapOn:
|
||||||
|
id: "ResidenceForm.StreetAddressField"
|
||||||
|
- inputText: "123 Main St"
|
||||||
|
- tapOn:
|
||||||
|
id: "ResidenceForm.CityField"
|
||||||
|
- inputText: "Austin"
|
||||||
|
- tapOn:
|
||||||
|
id: "ResidenceForm.StateProvinceField"
|
||||||
|
- inputText: "TX"
|
||||||
|
- tapOn:
|
||||||
|
id: "ResidenceForm.PostalCodeField"
|
||||||
|
- inputText: "78701"
|
||||||
|
- hideKeyboard
|
||||||
|
- tapOn:
|
||||||
|
id: "ResidenceForm.SaveButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Residence.List"
|
||||||
|
timeout: 15000
|
||||||
|
- assertVisible: "Maestro Test Residence"
|
||||||
30
.maestro/flows/04-create-task.yaml
Normal file
30
.maestro/flows/04-create-task.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Golden path: login → Tasks tab → Add → fill → Save.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Create task
|
||||||
|
tags:
|
||||||
|
- smoke
|
||||||
|
- task
|
||||||
|
---
|
||||||
|
- runFlow: 01-login.yaml
|
||||||
|
- tapOn:
|
||||||
|
id: "TabBar.Tasks"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Task.AddButton"
|
||||||
|
timeout: 10000
|
||||||
|
- tapOn:
|
||||||
|
id: "Task.AddButton"
|
||||||
|
- tapOn:
|
||||||
|
id: "TaskForm.TitleField"
|
||||||
|
- inputText: "Replace HVAC Filter"
|
||||||
|
- tapOn:
|
||||||
|
id: "TaskForm.DescriptionField"
|
||||||
|
- inputText: "Monthly filter replacement"
|
||||||
|
- hideKeyboard
|
||||||
|
- tapOn:
|
||||||
|
id: "TaskForm.SaveButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Task.List"
|
||||||
|
timeout: 15000
|
||||||
|
- assertVisible: "Replace HVAC Filter"
|
||||||
32
.maestro/flows/05-complete-task.yaml
Normal file
32
.maestro/flows/05-complete-task.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Golden path: login → open a task → Complete → fill completion fields → Submit → back on list.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Complete task
|
||||||
|
tags:
|
||||||
|
- regression
|
||||||
|
- task
|
||||||
|
---
|
||||||
|
- runFlow: 01-login.yaml
|
||||||
|
- tapOn:
|
||||||
|
id: "TabBar.Tasks"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Task.List"
|
||||||
|
timeout: 10000
|
||||||
|
- tapOn:
|
||||||
|
id: "Task.Card"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "TaskDetail.View"
|
||||||
|
timeout: 10000
|
||||||
|
- tapOn:
|
||||||
|
id: "TaskDetail.CompleteButton"
|
||||||
|
- tapOn:
|
||||||
|
id: "TaskCompletion.NotesField"
|
||||||
|
- inputText: "Completed via Maestro golden-path test."
|
||||||
|
- hideKeyboard
|
||||||
|
- tapOn:
|
||||||
|
id: "TaskCompletion.SubmitButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Task.List"
|
||||||
|
timeout: 15000
|
||||||
30
.maestro/flows/06-join-residence.yaml
Normal file
30
.maestro/flows/06-join-residence.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Golden path: login → Residences → Join → enter share code → Join.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Join residence by share code
|
||||||
|
tags:
|
||||||
|
- smoke
|
||||||
|
- residence
|
||||||
|
---
|
||||||
|
- runFlow: 01-login.yaml
|
||||||
|
- tapOn:
|
||||||
|
id: "TabBar.Residences"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Residence.JoinButton"
|
||||||
|
timeout: 10000
|
||||||
|
- tapOn:
|
||||||
|
id: "Residence.JoinButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "JoinResidence.ShareCodeField"
|
||||||
|
timeout: 10000
|
||||||
|
- tapOn:
|
||||||
|
id: "JoinResidence.ShareCodeField"
|
||||||
|
- inputText: "ABC123"
|
||||||
|
- hideKeyboard
|
||||||
|
- tapOn:
|
||||||
|
id: "JoinResidence.JoinButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Residence.List"
|
||||||
|
timeout: 15000
|
||||||
32
.maestro/flows/07-upload-document.yaml
Normal file
32
.maestro/flows/07-upload-document.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Golden path: login → Documents tab → Add → fill form → Save.
|
||||||
|
# File picker is exercised via FilePicker tap; OS chooser is platform-dependent
|
||||||
|
# and is skipped in CI by seeding a stub document.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Upload document
|
||||||
|
tags:
|
||||||
|
- smoke
|
||||||
|
- document
|
||||||
|
---
|
||||||
|
- runFlow: 01-login.yaml
|
||||||
|
- tapOn:
|
||||||
|
id: "TabBar.Documents"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Document.AddButton"
|
||||||
|
timeout: 10000
|
||||||
|
- tapOn:
|
||||||
|
id: "Document.AddButton"
|
||||||
|
- tapOn:
|
||||||
|
id: "DocumentForm.TitleField"
|
||||||
|
- inputText: "Home Inspection Report"
|
||||||
|
- tapOn:
|
||||||
|
id: "DocumentForm.NotesField"
|
||||||
|
- inputText: "Annual inspection — Maestro smoke test."
|
||||||
|
- hideKeyboard
|
||||||
|
- tapOn:
|
||||||
|
id: "DocumentForm.SaveButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Document.List"
|
||||||
|
timeout: 15000
|
||||||
|
- assertVisible: "Home Inspection Report"
|
||||||
24
.maestro/flows/08-theme-switch.yaml
Normal file
24
.maestro/flows/08-theme-switch.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Golden path: login → Profile → Settings → theme picker → select Ocean.
|
||||||
|
# Verifies persisted theme selection is applied (ThemeManager.setTheme).
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Theme switch to Ocean
|
||||||
|
tags:
|
||||||
|
- regression
|
||||||
|
- profile
|
||||||
|
---
|
||||||
|
- runFlow: 01-login.yaml
|
||||||
|
- tapOn:
|
||||||
|
id: "TabBar.Profile"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Profile.SettingsButton"
|
||||||
|
timeout: 10000
|
||||||
|
- tapOn:
|
||||||
|
id: "Profile.SettingsButton"
|
||||||
|
- tapOn: "Theme"
|
||||||
|
- tapOn: "Ocean"
|
||||||
|
- assertVisible: "Ocean"
|
||||||
|
- tapOn:
|
||||||
|
id: "Navigation.BackButton"
|
||||||
|
- assertVisible:
|
||||||
|
id: "TabBar.Profile"
|
||||||
20
.maestro/flows/09-notification-deeplink.yaml
Normal file
20
.maestro/flows/09-notification-deeplink.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Golden path: cold-launch via deeplink honeydue://task/<id> resolves to task detail.
|
||||||
|
# Requires a valid task id for the logged-in test account. CI can seed a fixture
|
||||||
|
# id via environment/script and interpolate; here we use "test-task-id" as a
|
||||||
|
# placeholder that a seed-step can replace.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Notification deeplink opens task
|
||||||
|
tags:
|
||||||
|
- regression
|
||||||
|
- deeplink
|
||||||
|
env:
|
||||||
|
TASK_ID: "test-task-id"
|
||||||
|
---
|
||||||
|
- runFlow: 01-login.yaml
|
||||||
|
- openLink: "honeydue://task/${TASK_ID}"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "TaskDetail.View"
|
||||||
|
timeout: 15000
|
||||||
|
- assertVisible:
|
||||||
|
id: "TaskDetail.CompleteButton"
|
||||||
29
.maestro/flows/10-widget-complete.yaml
Normal file
29
.maestro/flows/10-widget-complete.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Android-only: simulates a home-screen widget "complete task" tap by firing
|
||||||
|
# the widget's deeplink URI directly. Maestro does not render the Android home
|
||||||
|
# screen / App Widget host, so we exercise the underlying intent that the
|
||||||
|
# widget's PendingIntent targets (honeydue://task/<id>/complete). On iOS this
|
||||||
|
# flow is a no-op — iOS does not ship an equivalent widget surface yet.
|
||||||
|
appId: com.tt.honeyDue
|
||||||
|
name: Widget tap completes task (Android)
|
||||||
|
tags:
|
||||||
|
- android-only
|
||||||
|
- widget
|
||||||
|
env:
|
||||||
|
TASK_ID: "test-task-id"
|
||||||
|
---
|
||||||
|
- runFlow: 01-login.yaml
|
||||||
|
- openLink: "honeydue://task/${TASK_ID}/complete"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "TaskCompletion.SubmitButton"
|
||||||
|
timeout: 15000
|
||||||
|
- tapOn:
|
||||||
|
id: "TaskCompletion.NotesField"
|
||||||
|
- inputText: "Completed via widget simulation."
|
||||||
|
- hideKeyboard
|
||||||
|
- tapOn:
|
||||||
|
id: "TaskCompletion.SubmitButton"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "Task.List"
|
||||||
|
timeout: 15000
|
||||||
68
docs/maestro.md
Normal file
68
docs/maestro.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Maestro UI Flows
|
||||||
|
|
||||||
|
This directory's sibling `.maestro/` holds cross-platform UI tests driven by
|
||||||
|
[Maestro](https://maestro.mobile.dev/). The same YAML files run on both
|
||||||
|
Android and iOS because every selector uses an `id:` that resolves to an
|
||||||
|
`AccessibilityIds` test tag (Kotlin) or `AccessibilityIdentifiers` test tag
|
||||||
|
(Swift) — the two namespaces are kept in verbatim parity.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -Ls "https://get.maestro.mobile.dev" | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
maestro --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run the suite
|
||||||
|
|
||||||
|
With an Android emulator running (API 34+) or an iOS simulator booted and
|
||||||
|
the HoneyDue debug build installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# All flows (reads .maestro/config.yaml)
|
||||||
|
maestro test .maestro/flows/
|
||||||
|
|
||||||
|
# Single flow
|
||||||
|
maestro test .maestro/flows/01-login.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Override environment variables (see `09-notification-deeplink.yaml`,
|
||||||
|
`10-widget-complete.yaml`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
maestro test -e TASK_ID=123e4567-e89b-12d3-a456-426614174000 \
|
||||||
|
.maestro/flows/09-notification-deeplink.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available flows
|
||||||
|
|
||||||
|
| # | File | What it exercises |
|
||||||
|
|---|---|---|
|
||||||
|
| 01 | `01-login.yaml` | Email+password sign-in, land on tabs |
|
||||||
|
| 02 | `02-register.yaml` | New-user registration → verification stub |
|
||||||
|
| 03 | `03-create-residence.yaml` | Add a residence end-to-end |
|
||||||
|
| 04 | `04-create-task.yaml` | Add a task end-to-end |
|
||||||
|
| 05 | `05-complete-task.yaml` | Open task → complete → submit |
|
||||||
|
| 06 | `06-join-residence.yaml` | Join existing residence by share code |
|
||||||
|
| 07 | `07-upload-document.yaml` | Add a document |
|
||||||
|
| 08 | `08-theme-switch.yaml` | Profile → theme picker → Ocean |
|
||||||
|
| 09 | `09-notification-deeplink.yaml` | `honeydue://task/<id>` cold-launch |
|
||||||
|
| 10 | `10-widget-complete.yaml` | Android widget complete-intent (no-op on iOS) |
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- `maestro studio` opens an interactive inspector that lets you record
|
||||||
|
taps/typing and see every `testTag` the app exposes. Easiest way to
|
||||||
|
build new flows.
|
||||||
|
- `maestro test --debug-output /tmp/maestro` emits screenshots + logs for
|
||||||
|
each step — check there first when CI fails.
|
||||||
|
- Pre-seed a test user and fixture data via the API before running the
|
||||||
|
suite; the flows assume `testuser@example.com / TestPassword123!` exists.
|
||||||
|
- Keep new flows in sync with `AccessibilityIds.kt` (Kotlin) and
|
||||||
|
`AccessibilityIdentifiers.swift` (iOS) — these are the single source of
|
||||||
|
truth for every `id:` selector.
|
||||||
Reference in New Issue
Block a user