Skip to content

Scaffold Maestro E2E flows + CI workflow + Testing docs#2

Open
phwizard wants to merge 5 commits intomainfrom
tf/maestro-tests
Open

Scaffold Maestro E2E flows + CI workflow + Testing docs#2
phwizard wants to merge 5 commits intomainfrom
tf/maestro-tests

Conversation

@phwizard
Copy link
Copy Markdown
Contributor

Summary

Adds the Layer 2 (end-to-end) test scaffolding to this repo, paired with `ethora-sdk-android` PR #3 which adds Layer 1 (hermetic Compose UI tests in the SDK repo).

10 Maestro YAML flows covering ~80% of typical SDK usage patterns, plus a GitHub Actions workflow that runs them on every push, PR, and SDK release tag against `chat-qa.ethora.com`.

Files

Path Purpose
`.maestro/README.md` Layer-2 testing intro, repo layout, local + CI run instructions, flow-authoring conventions
`.maestro/config.yaml` Project-level Maestro config
`.maestro/fixtures/test-users.yaml` Shared test-user contract (placeholder values — real creds come from CI secrets, never committed)
`.maestro/flows/01-login-email.yaml` Email + password login
`.maestro/flows/02-login-jwt.yaml` Client-flow JWT login via `/users/client`
`.maestro/flows/03-list-rooms.yaml` `GET /chats/my` + unread counts
`.maestro/flows/04-send-text.yaml` Full XMPP send round-trip
`.maestro/flows/05-receive-text.yaml` MAM push delivery
`.maestro/flows/06-attach-file.yaml` `PendingMediaSendQueue` + chunked upload
`.maestro/flows/07-reconnect-airplane.yaml` Airplane-mode toggle → ConnectionStore recovery
`.maestro/flows/08-push-deep-link.yaml` Synthetic notification intent → room navigation
`.maestro/flows/09-logout-relogin.yaml` DataStore state isolation across sessions
`.maestro/flows/10-switch-app.yaml` Multi-tenant app-switcher (no remount)
`.github/workflows/maestro.yml` CI — macOS runner with android-emulator-runner; single API on PRs, matrix [26, 30, 34] on release tags
`README.md` New "Testing" section linking to `.maestro/README.md` and explaining the two-layer split

Why these specific flows

Every regression we've hit on Android in the last few weeks would have been caught by at least one of these:

  • `TOKEN_WRONG_TYPE` from setup writing the wrong JWT shape → caught by 01.
  • Preset XMPP `:5443` port hanging the connect → caught by 01 + 04.
  • Hardcoded BASE app ID in `MainActivity.kt` overriding the developer's profile → caught by 01 + 03.
  • `compileSdk = 37` unreachable platform SDK → blocks the build, caught before any flow runs.
  • LogStore refactor breaking compile → caught at the build step.

Future regressions on the cross-process boundary (push, reconnect, switch-app) are exactly the class Compose UI tests can't reach — that's why they live here, not in the SDK repo.

Status of each flow

All 10 are runnable as YAML, but several depend on helper assets that don't exist yet (left as inline TODOs):

  • `05-receive-text`: needs `.maestro/scripts/sendAsBob.js` (post a message via REST as the second user).
  • `06-attach-file`: needs a small bundled debug-asset image, or wire `pickFile` from gallery seeded by CI.
  • `07-reconnect-airplane`: needs `.maestro/scripts/toggleAirplaneMode.sh` (adb-driven).
  • `08-push-deep-link`: needs `.maestro/scripts/sendPushIntent.sh` (adb `am start` with intent extras).

The first green flow on CI can be `01-login-email` once repo secrets are populated. The rest can come online incrementally — the scaffold is here so the team has a single shape to extend.

Required repo secrets

Settings → Secrets and variables → Actions:

```
ETHORA_API_BASE_URL https://api.chat-qa.ethora.com/v1
ETHORA_APP_ID
ETHORA_APP_TOKEN
ETHORA_XMPP_HOST xmpp.chat-qa.ethora.com
ETHORA_XMPP_CONFERENCE conference.xmpp.chat-qa.ethora.com
ETHORA_XMPP_SERVER_URL wss://xmpp.chat-qa.ethora.com/ws

MAESTRO_TEST_EMAIL alice@ethora.com (test user on chat-qa)
MAESTRO_TEST_PASSWORD TestPass123
MAESTRO_TEST_USER_JWT client-flow JWT for 02-login-jwt
MAESTRO_TEST_ROOM_JID a known MUC room JID alice belongs to
MAESTRO_TEST_APPB_* second-app credentials for 10-switch-app
```

All values target chat-qa, not prod, in keeping with the product-code policy.

Test plan

  • Configure the secrets above on the repo.
  • Push to `main` and confirm the workflow runs (it'll fail on flows 02 and 05–08 until helpers / assets land — that's expected, not a regression).
  • `maestro test .maestro/flows/01-login-email.yaml` passes locally on a connected emulator with `.env` populated by `@ethora/setup`.
  • Sample's existing build + Compose tests still pass.

phwizard added 4 commits May 10, 2026 08:56
Adds .maestro/ with 10 placeholder flows covering ~80% of typical
SDK usage patterns:

  01-login-email           email + password login (catches the
                           TOKEN_WRONG_TYPE / wrong-base-URL class
                           of regressions we hit this week)
  02-login-jwt             client-flow JWT auth via /users/client
  03-list-rooms            GET /chats/my + unread counts
  04-send-text             full XMPP send round-trip
  05-receive-text          MAM push delivery (uses helper script
                           to send-as-bob via REST while alice
                           waits in-app)
  06-attach-file           PendingMediaSendQueue + chunked upload
  07-reconnect-airplane    airplane mode toggle, ConnectionStore
                           recovery without remount
  08-push-deep-link        synthetic notification intent → room
  09-logout-relogin        DataStore not leaking previous-user state
  10-switch-app            multi-tenant app-switcher (alice on app A
                           → charlie on app B without remount)

Each flow is a runnable YAML stub with realistic semantic anchors
based on the playground's actual UI. A few ($MAESTRO_TEST_*)
variables hold credentials, sourced from CI secrets so no real
data lives in source — fixtures/test-users.yaml documents the
contract.

CI workflow (.github/workflows/maestro.yml):

  - macOS runner (HAXM/hypervisor needed for emulator).
  - Single API level on PRs (34); full matrix [26, 30, 34] on
    release tags.
  - .env on the runner is composed from secrets, never checked in.
  - Failures upload Maestro recordings as artifacts (14d retention).

Both this repo's README and (paired commit on ethora-sdk-android
PR #3) the SDK README gain a "Testing" section explaining the
two-layer split:

  Layer 1 — hermetic component tests in ethora-sdk-android
            (chat-core/src/test, chat-ui/src/androidTest, ethora-component/src/test)

  Layer 2 — real-server smoke flows here, gating SDK release tags

The split keeps SDK tests fast and offline-runnable while pushing
network/integration flows to where the running app is.

Several flows reference helper scripts (.maestro/scripts/sendAsBob.js,
toggleAirplaneMode.sh, sendPushIntent.sh) and asset fixtures
(test image for 06-attach-file) that don't exist yet — left as
TODOs in the YAML comments. First green flow can be 01 or 04 with
a populated .env; the rest can fill in incrementally.
Addresses the inline TODOs in the original scaffold:

scripts/sendAsBob.js
  Real implementation. Maestro JS helper that POSTs /messages as bob
  via the REST API so flow 05-receive-text can verify alice receives
  it via XMPP without a second emulator. Uses Maestro's built-in
  `http` client; reads MAESTRO_APP_TOKEN / MAESTRO_TEST_BOB_JWT /
  MAESTRO_TEST_ROOM_JID from the env vars CI propagates from
  repo secrets.

scripts/sendPushIntent.sh
  Real implementation. Synthesises an `adb shell am start` with
  `--es notification_jid <jid>` to mimic an FCM-tap intent. Needed
  by 08-push-deep-link, which can't drive adb from inside Maestro.
  CI invokes the script before launching that flow; the flow then
  asserts on the resulting state.

assets/test-image.png
  74-byte 8x8 PNG seeded into /sdcard/Pictures/ by CI (with a
  MEDIA_SCANNER_SCAN_FILE broadcast to make it picker-visible) so
  06-attach-file can pick it from the gallery. Tiny by design —
  goal is just to exercise the upload path, not to test the codec.

Flow 07-reconnect-airplane rewritten
  Replaced the adb-driven airplane-mode toggle with the app's own
  SETUP / Disconnect / Connect buttons. Tests the same SDK
  reconnect path (ChatConnectionStore transitions, XMPP client
  teardown, MAM history survives) without depending on a shell
  command Maestro can't run. OS-level network simulation can come
  later as a separate flow invoked by a CI pre-step that toggles
  wifi via adb.

Flow 08-push-deep-link rewritten
  Now an assertion-only flow with `clearState: false` — assumes
  the launcher intent is the synthesised notification, asserts
  that EthoraChatProvider deep-links into the right room. CI
  fires the intent via sendPushIntent.sh before invoking maestro.

CI workflow updates (.github/workflows/maestro.yml)
  - Runs all 10 flows in two batches (01-07 + 09-10 first, then 08
    after the synthetic intent) so the deep-link assertion has
    deterministic launcher state.
  - Pushes test-image.png to /sdcard/Pictures + broadcasts media
    scan before the suite starts.
  - Adds env vars sendAsBob.js needs (MAESTRO_API_BASE_URL,
    MAESTRO_APP_TOKEN, MAESTRO_TEST_BOB_JWT) and updates the
    secrets header comment.

.maestro/README.md
  Repo layout updated to show assets/ + scripts/ directories with
  a one-line description per file. Added a "Why some helpers live
  outside the flow YAML" callout explaining the
  JS-runtime-can't-shell-out constraint.
…m/wrong-password

Expands the suite from 10 → 15 flows, targeting common interactions
beyond the basic login/send/receive happy path. Each is a runnable
YAML stub with realistic semantic anchors; selectors marked
'optional: true' where the SDK's UI is in flux so the flow doesn't
hard-fail on a renamed accessibility id, just the missing assertion.

11-login-wrong-password
  Negative path. Catches: error response not surfaced to UI, retry
  loop on 401, accidental login under a stale cached token. No
  extra secrets needed beyond MAESTRO_TEST_EMAIL.

13-message-edit
  Long-press → Edit → ChatInput switches to "Edit message..." mode
  → change text → Send → assert bubble updates in place. Catches
  editText prop not flowing into ChatInput, edit calling POST
  instead of PUT, optimistic update not reconciling with ack.

14-message-delete
  Long-press → Delete → confirm → assert bubble removed or
  replaced by tombstone. Catches delete RPC silently failing
  while UI optimistically removes, MAM still returning the
  deleted message on rejoin.

15-message-reaction
  Long-press → React → 👍 → assert reaction visible with count 1.
  Catches: reaction not stored in Message.reaction map, picker
  rendering with no preset emoji, count not incrementing on
  re-react, reaction stripping from MAM history.

16-create-room
  Tap "+" → Create dialog → name → assert room visible and
  navigable. Sends a follow-up message to verify the new room
  is fully bootstrapped (XMPP presence joined, write perms
  correct). Catches: create-room RPC failing silently, JID
  collision on duplicate name.

Flow 12 reserved for typing-indicator — needs a helper that pokes
XMPP composing-state on bob's behalf (similar to sendAsBob.js but
on the typing endpoint), left for a follow-up.

Updates: .maestro/README.md flow listing + .github/workflows/maestro.yml
to include the new flows in the CI run order.
…overage table

Brings the suite to 19 flows total (slot 12 still reserved for
typing-indicator pending a composing-state helper). Adds a coverage
table to both README.md and .maestro/README.md so the team can see
at a glance what each flow asserts and what regression classes it
catches.

17-search-rooms
  Types into RoomListView's SearchBar, asserts the queried room
  remains visible. Catches: predicate not case-insensitive, list
  not re-rendering on query change, "no results" empty-state
  never appearing.

18-multi-message-rapid
  Five back-to-back sends, asserts all five are visible in the
  conversation. Catches: out-of-order ack reordering, optimistic
  UI dropping bubbles, ChatInput debounce eating taps.

19-room-info
  Opens ChatInfoScreen via the room title / info affordance,
  asserts the participants list shows the current user and a
  Leave control. Back-navigates and confirms we land back on
  the conversation, not the room list. Several selectors marked
  optional: true since the SDK's ChatInfoScreen UI is in flux —
  flow still composes a meaningful skeleton when those land.

20-offline-pending-resend
  Disconnects via SETUP/Disconnect, attempts a send, reconnects,
  asserts the message ultimately lands. Includes an optional
  "Tap to retry" tap in case the SDK requires manual retry
  rather than auto-resend on reconnect — covers both contracts
  with one flow.

CI workflow (.github/workflows/maestro.yml) updated to include the
four new flows in the run order.

The flow files reference shared semantic anchors that don't yet
exist in source (id: "chat_input", id: "chat_send_button",
id: "rooms_search_input", etc.). Marked optional: true so flows
still execute on a CI run today, but they assert nothing useful
until those testTags land in the SDK's Compose tree. Adding
semantic testTags to chat-ui composables is the highest-leverage
source change to unblock these — flagged in the README.
Adds a Layer 1 / Layer 2 mapping table showing how this Android
sample's Maestro flows fit into the 4-platform stack (sdk-android,
sdk-swift, sample-android, sample-swift, chat-component, app-reactjs)
and how the selector-string contract (testTag / accessibilityIdentifier
/ data-testid) couples them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant