Skip to content

CT-2322 Run Maestro E2E tests on Sauce Labs - #132

Closed
Jameson13B wants to merge 3 commits into
v4from
jameson/CT-2322
Closed

Jameson13B wants to merge 3 commits into
v4from
jameson/CT-2322

Conversation

@Jameson13B

@Jameson13B Jameson13B commented Oct 21, 2026 •

Copy link
Copy Markdown

Refs CT-2322

Runs the example app's Maestro flows on a Sauce Labs device from GitHub Actions, instead of on a GitHub-hosted Android emulator (30+ minutes, see #117), using maestro-runner, Sauce's community integration that runs Maestro flows through Appium.

How it works

  1. build-android builds the SDK and the example app as a release APK (JS bundled, no Metro) with EXPO_PUBLIC_PROXY_BASE_URL=http://sdk-e2e-proxy:8089. No secrets needed, so it runs on every matching PR, including from forks.
  2. test-android starts the SSO proxy on the runner, points sdk-e2e-proxy at 127.0.0.1 in /etc/hosts, opens a Sauce Connect tunnel that carries only that host, uploads the APK to App Storage and runs example/maestro/ on a Sauce Android emulator (or a real device via workflow_dispatch). The report is uploaded as an artifact with the Sauce access key redacted, and the APK is deleted from App Storage afterwards.
  3. Without the secrets (forks, Dependabot, or before they're configured), the test job is skipped with a notice naming the missing secrets, and the check passes.

Details, required secrets and a "When it fails" section are in E2E_TESTING.md.

Changes to the example app

  • The proxy URL moved from six hardcoded copies to example/src/shared/proxy.ts, overridable with EXPO_PUBLIC_PROXY_BASE_URL. Local defaults are unchanged.
  • usesCleartextTraffic is enabled through expo-build-properties, so the release build can talk http to the proxy.
  • example/sauce/ holds the Appium capabilities per target and a small lockfile pinning the SSO proxy and maestro-runner.

Before merging: secrets

The test-android job has not run yet, because the repo has no Sauce Labs credentials. It needs these repository secrets: SAUCE_USERNAME, SAUCE_ACCESS_KEY (ideally a service user; the mobile team owns the Sauce account), MX_CLIENT_ID and MX_API_KEY (the int E2E test client from .mx-sso-proxy-rc.example.yml, or a dedicated one). Once they exist, re-run the workflow on this PR and expect to tune the flows on the first real run (for example a Chrome first-run sheet in the OAuth Custom Tab).

Manual runs from the Actions tab only become available once the workflow file is on master; until then gh workflow run e2e-sauce-labs.yml --ref <branch> -f target=android-real-device is the way.

Acceptance criteria

  • The example app's proxy URL can be set at build time; local dev still defaults to 10.0.2.2:8089 / localhost:8089. Verified: the CI-built APK's Hermes bundle contains http://sdk-e2e-proxy:8089; unset falls back to the old values.
  • A release APK (JS bundled, no Metro) builds in CI and can reach an http proxy. Verified: build-android passes; the manifest has usesCleartextTraffic.
  • CI runs the SSO proxy on the runner with secrets and no interactive prompt. Verified locally: with </dev/null and missing settings it exits 1 instead of prompting; the restart loop and probe behave as intended. Reachability from the Sauce device through Sauce Connect is unverified until the secrets exist.
  • The Maestro flows run on Sauce Labs via maestro-runner, and the job fails when a flow fails. Needs the secrets. The flows pass maestro-runner lint, and its exit codes were checked in the source.
  • The Sauce access key isn't published in uploaded artifacts. The redaction step fails closed and the upload depends on it; tested locally with filenames containing spaces and unreadable files.
  • PRs from forks, or runs without secrets, skip cleanly instead of failing. Verified on this PR.
  • E2E_TESTING.md documents the CI setup, the required secrets and how to debug a failure.

Review

Four-lens AI review posted as a comment below; everything it raised is addressed except the items marked open there (NDK download time, Chrome first-run on the OAuth tab, Sauce session-creation failures), which need a real run to judge.

@Jameson13B

Jameson13B commented Oct 21, 2026 •

Copy link
Copy Markdown
Author

🤖 AI code review (4 lenses)

Expand review

🤖 Draft findings from four parallel AI reviewers (security, CI reliability, Android/Expo build, clarity), not a graded verdict; a human review is still needed. Raised by @jameson.brown via /walkaway. The full synthesis and raw reports are kept locally.

Verdict: the design holds up on every lens: pull_request + a secrets gate + a build job with no secrets, the EXPO_PUBLIC_* inlining, cleartext in the release manifest, signing, identifiers, the App Storage API calls, and maestro-runner's exit codes. One thing was actually broken (the build job); the rest was latent and would have shown up on the first run with secrets. Everything below is addressed in 7a49083 and a6a70fb unless marked otherwise.

Convergent findings (≥2 lenses)

  • [Critical, fixed] .github/workflows/e2e-sauce-labs.yml — android-actions/setup-android@v3 failed on the removed tools package, so the build job had never run.

    Details
    • Why it matters: nothing after that step (prebuild, Gradle, the artifact) had been exercised in CI.
    • Fix: dropped the step; ubuntu-latest already has the SDK. The release APK now builds (21 min cold with 4 ABIs; trimmed to x86_64 + arm64-v8a).
    • Lenses: CI, Android
  • [High, fixed] workflow, "Remove credentials from the report" — the scrub failed open (upload on always(), whitespace-split xargs, no final check) and deleted maestro-runner.log, the main debugging file.

    Details
    • Why it matters: maestro-runner 1.1.29 writes the Appium URL, access key included, into its log, and the report is a public artifact.
    • Fix: redact in place with null-separated filenames, fail if any copy is left, upload only when that step succeeded. Tested locally against spaces in filenames and unreadable files.
    • Lenses: Security, Clarity, CI
  • [High, fixed] workflow, "Start SSO proxy" — @mxenabled/sso-api-proxy 1.3.4 exits on the first MX API error (unawaited promise), its crash output includes the client credentials, and the workflow printed the raw log.

    Details
    • Why it matters: a single 429/5xx mid-run would fail every later flow in a way that looks like a UI regression, and the printed log could expose the credentials (reproduced locally by both the Security and CI reviewers).
    • Fix: restart loop around the proxy, --max-time on the probe, and the log step prints only request lines and status codes. The real fix (await respondWithSsoUrl) belongs in the proxy repo.
    • Lenses: CI, Security
  • [Medium, fixed] E2E_TESTING.md — "run it from the Actions tab" doesn't work until the workflow file is on the default branch (master); this PR targets v4.

    Details

    Docs now say so and give the gh workflow run --ref command. Lenses: CI, Clarity

  • [Medium, fixed] workflow, Sauce Connect step — without tunnelDomains, all device traffic went through the runner, and proxyLocalhost: allow exposed every localhost port.

    Details

    Now tunnelDomains: sdk-e2e-proxy, so only proxy calls go through the tunnel. Lenses: Security, CI

Unique findings

  • [Medium, fixed] Secrets were in the job-level env, visible to every step including npm ci lifecycle scripts; maestro-runner was fetched by npx with no integrity lock. (Security)

    Details

    Secrets are now step-scoped; the proxy and maestro-runner are pinned in example/sauce/package.json + lockfile and installed with --ignore-scripts; sauce-connect-action is pinned to a SHA; telemetry is off.

  • [Medium, fixed] Caches never warmed because nothing ran on the base branch; the path filter missed the SDK build config; the concurrency group ignored the dispatch target; the Maestro step had no timeout. (CI)

  • [Medium, open] RN 0.81 pins NDK 27.1, which isn't on the runner image, so Gradle downloads it every run. (Android)

    Details

    Left as is for now; watch the build time. Caching $ANDROID_HOME/ndk/27.1.12297006 is the fix if it matters.

  • [Watch-out, open] The OAuth Custom Tab on a fresh Sauce device may show Chrome's first-run sheet over "Authorize". (Android, speculative) Check on the first real run.

  • [Watch-out, open] A failed Sauce session creation (no free concurrency, emulator didn't boot) fails the job the same way a test failure does. (CI) Documented under "When it fails": re-run.

  • [Info] PulseWidget is exported by v4 at runtime; only its .d.ts is missing (pre-existing, global JSX with React 19 types), so the Pulse flow can pass. (Android)

  • [Nit, fixed] .mx-sso-proxy-rc.yml, the natural copy of the example file, wasn't gitignored in a public repo. (Security)

-- Jameson's Agent

The example app hardcoded the SSO proxy at 10.0.2.2:8089 (Android) or
localhost:8089 in six places. Move it to src/shared/proxy.ts and let
EXPO_PUBLIC_PROXY_BASE_URL override it, so a CI build can point a Sauce
Labs device at the proxy through Sauce Connect. Local development keeps
the same defaults.

Release builds bundle the JS (no Metro), and Android blocks cleartext
http in release builds by default, so enable usesCleartextTraffic via
expo-build-properties. Only the example app is affected.
Add a workflow that builds the example app as a release APK, starts the
SSO proxy on the runner, opens a Sauce Connect tunnel and runs
example/maestro on a Sauce Labs Android emulator (or a real device when
run manually) with maestro-runner, Sauce's community integration for
Maestro flows.

The APK is built in its own job, which needs no secrets, so build
breaks show up on every PR even while the Sauce Labs job is skipped
for lack of secrets (forks, Dependabot, or before they're configured).
The runner image already has the Android SDK; setup-android@v3 fails
on the removed 'tools' package, so it isn't used.

The app reaches the proxy through an /etc/hosts alias, because Sauce
Connect can't proxy localhost to real devices, with proxyLocalhost:
allow because the alias resolves to 127.0.0.1.

E2E_TESTING.md lists the required secrets.
Credentials
- Redact the Sauce access key in the Maestro report instead of deleting
  files, fail the step if any copy is left, and only upload the report
  when that step succeeded. The old scrub could fail open and threw away
  maestro-runner.log, the main debugging file.
- Pass the Sauce and MX secrets only to the steps that use them, and pin
  saucelabs/sauce-connect-action to a SHA since it receives the key.
- Print only request lines from the SSO proxy log; its crash output
  includes the client credentials.
- Gitignore every .mx-sso-proxy-rc* except the example file.

Reliability
- Pin the SSO proxy and maestro-runner in example/sauce/package.json
  with a lockfile, installed with --ignore-scripts, instead of a full
  root npm ci plus npx.
- Restart the SSO proxy when it exits: version 1.3.4 dies on the first
  MX API error. Give the readiness probe a per-request timeout.
- Only route the proxy alias through the tunnel (tunnelDomains).
- Build only the x86_64 and arm64-v8a ABIs; a cold 4-ABI build took
  21 minutes in CI.
- Run on pushes to v4 and master so the caches warm for PRs; include
  the SDK build config in the path filter; keep manual runs for
  different targets from cancelling each other; time-box the Maestro
  step; turn off maestro-runner telemetry.

Docs: how to run it manually (not from the Actions tab until the file
is on master), valid SAUCE_REGION values, and a "When it fails" section.
@Jameson13B

Copy link
Copy Markdown
Author

Closing: this was opened against the wrong repo. The React Native Connect SDK lives in mxenabled/react-native-connect-widget-sdk now; the same change is mxenabled/react-native-connect-widget-sdk#39.

-- Jameson's Agent

@Jameson13B Jameson13B closed this Oct 22, 2026
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