diff --git a/.maestro/config.yaml b/.maestro/config.yaml new file mode 100644 index 0000000..8dd915a --- /dev/null +++ b/.maestro/config.yaml @@ -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 diff --git a/.maestro/flows/01-login.yaml b/.maestro/flows/01-login.yaml new file mode 100644 index 0000000..e522bbd --- /dev/null +++ b/.maestro/flows/01-login.yaml @@ -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" diff --git a/.maestro/flows/02-register.yaml b/.maestro/flows/02-register.yaml new file mode 100644 index 0000000..20e389e --- /dev/null +++ b/.maestro/flows/02-register.yaml @@ -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" diff --git a/.maestro/flows/03-create-residence.yaml b/.maestro/flows/03-create-residence.yaml new file mode 100644 index 0000000..b4d8040 --- /dev/null +++ b/.maestro/flows/03-create-residence.yaml @@ -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" diff --git a/.maestro/flows/04-create-task.yaml b/.maestro/flows/04-create-task.yaml new file mode 100644 index 0000000..2e95996 --- /dev/null +++ b/.maestro/flows/04-create-task.yaml @@ -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" diff --git a/.maestro/flows/05-complete-task.yaml b/.maestro/flows/05-complete-task.yaml new file mode 100644 index 0000000..9d4a0d4 --- /dev/null +++ b/.maestro/flows/05-complete-task.yaml @@ -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 diff --git a/.maestro/flows/06-join-residence.yaml b/.maestro/flows/06-join-residence.yaml new file mode 100644 index 0000000..983de75 --- /dev/null +++ b/.maestro/flows/06-join-residence.yaml @@ -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 diff --git a/.maestro/flows/07-upload-document.yaml b/.maestro/flows/07-upload-document.yaml new file mode 100644 index 0000000..59b3073 --- /dev/null +++ b/.maestro/flows/07-upload-document.yaml @@ -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" diff --git a/.maestro/flows/08-theme-switch.yaml b/.maestro/flows/08-theme-switch.yaml new file mode 100644 index 0000000..26eee52 --- /dev/null +++ b/.maestro/flows/08-theme-switch.yaml @@ -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" diff --git a/.maestro/flows/09-notification-deeplink.yaml b/.maestro/flows/09-notification-deeplink.yaml new file mode 100644 index 0000000..2e9cbae --- /dev/null +++ b/.maestro/flows/09-notification-deeplink.yaml @@ -0,0 +1,20 @@ +# Golden path: cold-launch via deeplink honeydue://task/ 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" diff --git a/.maestro/flows/10-widget-complete.yaml b/.maestro/flows/10-widget-complete.yaml new file mode 100644 index 0000000..1efe043 --- /dev/null +++ b/.maestro/flows/10-widget-complete.yaml @@ -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//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 diff --git a/docs/maestro.md b/docs/maestro.md new file mode 100644 index 0000000..f6511bc --- /dev/null +++ b/docs/maestro.md @@ -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/` 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.