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