mattermost-mobile-upstream/detox
Guillermo Vayá 05f4e6769e
fix(e2e): make MM-T5611 iOS browser hand-off assert on Safari's breadcrumb (#10043)
* fix(e2e): anchor MM-T5611 iOS browser hand-off on Safari's breadcrumb

channel_bookmark_link_external.yml asserted that the channel's draft input
disappeared after tapping a link bookmark on iOS, on the assumption that an
in-app SFSafariViewController was presented over the channel.

It isn't. openLink -> tryOpenURL -> Linking.openURL hands off to the external
Safari app, so Mattermost is only suspended, and Maestro's iOS hierarchy keeps
reporting the suspended app's elements alongside Safari's. The assertion can
therefore never become true.

Run 31715496229 (maestro-ios) failed on exactly that assertion after 5m10s,
even though sim-device.log shows the tap worked: the app logged
'appEntersBackground' and 'Application suspended' 4s later.

Assert on Safari's "back to Mattermost" breadcrumb pill instead, then tap it to
return - the same anchor help_url.yml already uses, and the reason help_url.yml
only asserts notVisible on Android. Documented the rule in GUIDELINES.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(e2e): re-skip MM-T4899_2 on iOS - full view has no horizontal scroll

PR #9989 un-skipped four iOS tests in markdown_table.e2e.ts. It fixed the
NavigationHeader problems behind three of them, but MM-T4899_2 was skipped for a
different reason - "expanded-table horizontal scroll cannot reveal the right
column" - which is still true.

MarkdownTable.shouldRenderAsFlex(isFullView=true) returns true for a 3-4 column
table on a phone, so the expanded table is flex-fitted to the viewport rather
than laid out at getTableWidth(true). table.scroll_view therefore has no
horizontal scrollable extent and Detox fails the first scroll with "Unable to
scroll right"; testFnFailure.png shows the table still at offset 0. MM-T4899_3
scrolls fine because its 8-column table takes the non-flex branch and does get an
explicit width.

Deterministic, not a flake: reproduced on main 10207015 (run 31700057852) and on
run 31715496229.

Re-enable once the clipped right column in the flex-rendered full view is
addressed - that is an app layout question, not a test-harness one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(e2e): scroll MM-T4899_2 with scrollTo('right') instead of a 150pt gesture

Reverts the iOS skip added in the previous commit - it was based on a wrong
diagnosis - and fixes the test properly instead, keeping iOS coverage.

I had concluded the expanded 3-column table has no horizontal scrollable extent
on iOS. The view hierarchy Detox captured at the moment of failure says
otherwise:

  RCTEnhancedScrollView          x=0    w=402
  RCTParagraphComponentView      x=392  w=174   label="Right header that wraps"
  _UIScrollViewScrollIndicator   label="Horizontal scroll bar, 2 pages"

The content is 576pt wide (3 * CELL_MAX_WIDTH) against a 402pt viewport and
UIKit reports a two-page horizontal scroll bar, so there is ~174pt of real
extent. The app is fine.

What actually fails is the gesture: Detox refuses a 150pt scroll that close to
the edge and throws "Unable to scroll right" without moving at all - the same
hierarchy shows the table still at offset 0. MM-T4899_5 uses the identical
whileElement(...).scroll(150, 'right') form and passes because its 8-column
table has ~558pt of extent to work with.

scrollTo('right') jumps to the content edge with no gesture and is already used
on this same scroll view by MM-T4899_3, which passes on iOS in the same shard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* revert(e2e): drop both MM-T4899_2 attempts, restore main's version

Neither attempt worked, and the second was based on a diagnosis that the third
run disproved. Restoring the file to main so this PR carries only the verified
Maestro fix.

What the runs actually show for the 3-column full-view table (content 576pt vs a
402pt viewport, so ~174pt of horizontal extent - UIKit reports "Horizontal
scroll bar, 2 pages" in every capture, so the extent is real):

  MM-T4899_2  scroll(150,'right')   fail (31715496229)   -
  MM-T4899_2  scrollTo('right')     -                    fail, no-op (31732059826)
  MM-T4899_4  scroll(150,'right')   PASS                 fail
  MM-T4899_5  scroll(150,'right')   pass (~558pt extent) fail
  MM-T4899_3  scrollTo('right')     pass (~558pt extent) pass

MM-T4899_4 is the same 3-column/174pt case as _2 and it passed in one run and
failed in the other. So this is not "iOS cannot scroll" (my first diagnosis) and
not "the gesture is too big but scrollTo works" (my second) - the marginal-extent
case is simply nondeterministic under Detox, and a single green run would not
prove any fix.

Deciding what to do about _2/_4 needs a test-design call from the file's owners,
not another speculative round of CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(e2e): allow 30s for the post-hand-off return in MM-T5611

On iOS the final channel_header.bookmarks.list assertion now runs immediately
after returning from Safari, so the app is resuming from suspension rather than
finishing an in-app transition. CI run 31732059826 cleared the breadcrumb
assertion and the breadcrumb tap, then failed here on the 10s budget.

help_url.yml already allows 30s for the same post-hand-off return; match it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(e2e): drop the GUIDELINES.md note from this PR

Keeps the branch to the MM-T5611 fix only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 17:05:00 +02:00
..
android_emulator test(detox): re-submit RF→Detox migration without unintended UI changes (#9847) 2026-06-19 22:44:02 +05:30
e2e MM-70051 Fixing issues with system classifications (#9989) 2026-08-11 11:17:39 +03:00
maestro fix(e2e): make MM-T5611 iOS browser hand-off assert on Safari's breadcrumb (#10043) 2026-08-14 17:05:00 +02:00
provision fix(e2e): Quarantine proven flakes and harden Detox harness (#9971) 2026-07-30 02:40:23 +05:30
scripts chore(e2e): Cache app builds. Skip E2E on docs only PR. (#9930) 2026-08-04 19:13:39 +05:30
utils chore(e2e): Cache app builds. Skip E2E on docs only PR. (#9930) 2026-08-04 19:13:39 +05:30
.babelrc Barebones code for v2 2020-11-17 14:20:58 -03:00
.detoxrc.json test(detox): re-submit RF→Detox migration without unintended UI changes (#9847) 2026-06-19 22:44:02 +05:30
CLAUDE.md ci(e2e): per-job TSIO report groups, commit statuses, and webhook sidecar (#9969) 2026-07-29 13:45:06 +05:30
create_android_emulator.sh ci(e2e): per-job TSIO report groups, commit statuses, and webhook sidecar (#9969) 2026-07-29 13:45:06 +05:30
generate_unified_report.js test(detox): re-submit RF→Detox migration without unintended UI changes (#9847) 2026-06-19 22:44:02 +05:30
inject-detox-settings.js Revert "e2e(detox): Rainforest to Detox migration (#9790)" (#9834) 2026-06-15 17:57:01 +02:00
package-lock.json fix(e2e): Quarantine proven flakes and harden Detox harness (#9971) 2026-07-30 02:40:23 +05:30
package.json fix(e2e): Quarantine proven flakes and harden Detox harness (#9971) 2026-07-30 02:40:23 +05:30
README.md fix(e2e): Quarantine proven flakes and harden Detox harness (#9971) 2026-07-30 02:40:23 +05:30
save_maestro_zephyr.js feat(e2e): Run e2e against different server version when rc branch is pushed (#9800) 2026-07-07 18:02:13 +05:30
save_report.js feat(e2e): Integrate TSIO reporting (#9925) 2026-07-23 06:43:28 +00:00
tsconfig.json test(maestro): Add maestro test framework (#9788) 2026-06-26 15:08:55 +05:30
webhook_server.js Mattermost Blocks (#9764) 2026-06-29 18:42:16 +02:00

How to Run Detox Tests

This guide will help you set up and run Detox tests for your project.

Install Dependencies

First, navigate to the root directory of your project and install the necessary dependencies by running:

npm install

navigate to the detox folder and run npm install

Configure your server details

detox/e2e/support/test_config.ts defaults to one local server on port 8065. For iOS, both the test runner and simulator use http://localhost:8065. For Android, the test runner uses http://localhost:8065 while the emulator uses http://10.0.2.2:8065.

If you use a remote server, set the logical site variable to a URL reachable from both the host test runner and the device:

  export SITE_1_URL="https://your-server.example.com"
  export ADMIN_USERNAME="your-username"
  export ADMIN_PASSWORD="your-password"
  export ADMIN_EMAIL="your-email"

You can instead create a detox/.env file:

  # detox/.env
  SITE_1_URL=https://your-server.example.com
  ADMIN_USERNAME=your-username
  ADMIN_PASSWORD=your-password
  ADMIN_EMAIL=your-email@example.com

Note: no need to provide all variables, only the ones that differ.

SITE_2_URL and SITE_3_URL are only needed for tests that add distinct Mattermost servers, such as server_login/server_list.e2e.ts. If they are unset, their URL helpers alias site 1 and tests that require distinct servers are skipped. The five-server ANDROID_SITE_* and IOS_SITE_* topology is assigned by CI and is not required for ordinary local runs.

Android

Build Detox Android App

To build the Detox Android app, navigate to the detox folder and run:

npm run e2e:android-build

The debug apk will be built and available at android/app/build/outputs/apk/debug/app-debug.apk

Run Detox Android Tests

Create emulator

./create_android_emulator.sh SDK_VERSION AVD_NAME

# example ./create_android_emulator.sh 34 pixel_5a_avd
# example ./create_android_emulator.sh 34 pixel_5a_avd --headless
# If we want to see the emulator logs. Run it in debug mode example ./create_android_emulator.sh 34 pixel_5a_avd --debug

To execute the Detox tests on Android, navigate to the detox folder and run:

npm run e2e:android-test

# To run a particular tests

npm run e2e:android-test <path to test file>

Local runs now default to a lower-memory setup so they coexist better with other tools:

  • Detox uses 1 worker unless you set DETOX_MAX_WORKERS.
  • Detox caps Node heap at 4096MB unless you already set NODE_OPTIONS.
  • Android Detox builds cap Gradle heap at 3072MB and use at most 2 Gradle workers unless you override MM_GRADLE_XMX_MB or MM_GRADLE_WORKERS_MAX.
  • Android emulator RAM defaults to 3072MB when created or refreshed; override with MM_ANDROID_EMULATOR_RAM_MB.

iOS

Build iOS Simulator

To build the iOS simulator for Detox, from the project folder run:

npm run e2e:ios-build

This will build the Simulator .zip file at the root folder.

Create a folder named mobile-artifacts at the project root. Move the zip file under mobile-artifacts and unzip it there.

# From project root
mkdir mobile-artifacts

Configure your local

Update detox/.detoxrc.json to specify which device you want to run it. Change:

 "device": {
     "type": "__DEVICE_NAME__",
     "os": "__DEVICE_OS_VERSION__"
 },

to something like:

 "device": {
     "type": "iPhone 17 Pro",
     "os": "iOS 26.4"
 },

To get your values run xcrun simctl list devices to find both the device and iOS version.

Run iOS Tests

To execute the Detox tests on iOS, navigate to the detox folder and run:

npm run e2e:ios-test

# To run a particular tests

npm run e2e:android-test path to test file.

Disabling Password Autofill

from iOS v26 password autofill can interfere with login tests by automatically filling credentials. To disable this feature on your simulator:

# Interactive mode - select simulator from list
npm run e2e:ios-disable-autofill

# Or specify simulator ID directly
npm run e2e:ios-disable-autofill -- --simulator-id SIMULATOR_UDID

When to use this:

  • if using iOS 26 or later
  • Before running iOS E2E tests if you notice password fields being auto-filled
  • When login tests fail unexpectedly due to autofill interference
  • After creating a new iOS simulator for testing

Note: CI environments automatically disable this setting, so this is only needed for local development.

Finding your simulator ID:

xcrun simctl list devices | grep Booted

TIP : For iOS, you can download the simulator from ~Mobile: Test build or ~Release: Mobile Apps channel in the community.

Results

The Local Runs generate artifacts under detox/artifacts/ios-debug-** or detox/artifacts/android-debug-**. You can see the html report, failure screenshot under that folder.

Webhook sidecar (mm_blocks / interactive dialog specs)

The mm_blocks_* and interactive-dialog specs register integrations that the Mattermost server calls back into. detox/webhook_server.js serves those callbacks on :3000, and the server must be able to reach it:

  • Local server (SITE_1_URL=http://localhost:8065): no tunnel needed, the server reaches 127.0.0.1:3000 directly.

    cd detox && npm run start:webhook
    
  • Remote/cloud test server: the server cannot reach your machine, so the sidecar needs a public HTTPS origin. detox/scripts/start_webhook_sidecar.sh starts webhook_server.js and resolves that origin, in priority order:

    Env Behaviour
    WEBHOOK_PUBLIC_BASE_URL Use an already-routable HTTPS origin as-is (named tunnel, ngrok, reverse proxy). Preferred.
    WEBHOOK_PUBLIC_BASE_URL + CLOUDFLARED_TUNNEL_TOKEN Run a named Cloudflare tunnel for that hostname. The token is passed via TUNNEL_TOKEN because argv is world-readable.
    neither Fall back to a trycloudflare.com quick tunnel. Time-boxed and DNS-flaky, so treat it as a last resort.
    cd detox
    SITE_1_URL="https://your-test-server" \
    WEBHOOK_PUBLIC_BASE_URL="https://your-tunnel-hostname" \
    bash scripts/start_webhook_sidecar.sh
    

    Pin a different cloudflared with CLOUDFLARED_VERSION; the script defaults to a known-good release rather than latest.

The script always exits 0 — it exports WEBHOOK_SIDECAR_READY and WEBHOOK_CALLBACKS_REACHABLE instead of failing, and specs that need a webhook fail fast on their own health check. A quick tunnel passes outbound health checks but does not reliably deliver server-to-sidecar callbacks, so it reports WEBHOOK_CALLBACKS_REACHABLE=false.

Playbooks Tests (AI-Powered Testing)

The Playbooks tests leverage AI-powered testing through the Wix Pilot framework, enabling natural language test creation and execution.

How It Works

The Playbooks tests utilize the pilot library, which connects to Anthropic Claude via the ClaudePromptHandler. This innovative approach allows you to:

  • Write tests using natural language prompts
  • Automatically translate prompts into executable test steps
  • Maintain tests more intuitively through conversational interfaces
  • Handle complex scenarios with AI-generated test logic

The system integrates seamlessly with your existing Detox test infrastructure while adding AI capabilities.

Best Practices

For reliable and effective Playbooks tests, follow these guidelines from the Pilot Best Practices Guide:

  1. Prompt Clarity:

    • Write clear, concise prompts
    • Use specific action-oriented language
    • Avoid ambiguous phrasing
  2. Test Design:

    • Keep prompts focused on single scenarios
    • Use deterministic language to prevent flaky tests
    • Structure complex tests as sequences of simple prompts
  3. Maintenance:

    • Review AI-generated test steps before committing
    • Version control both prompts and generated code
    • Monitor test stability regularly
  4. Integration:

    • Combine AI tests with traditional assertions
    • Use AI for complex scenarios, traditional methods for simple verifications
    • Document which tests are AI-generated

Getting Started

To run Playbooks tests:

  1. Set up your environment:

    export ANTHROPIC_API_KEY='your-api-key-here'
    
  2. Run tests normally:

    • The system will automatically initialize the ClaudePromptHandler when the API key is detected
    • No additional configuration is required beyond the API key
  3. Execution examples:

    # Run all Playbooks tests
    npm run e2e:android-test tests/playbooks/
    
    # Run a specific Playbook test
    npm run e2e:ios-test tests/playbooks/playbooks_basic.e2e.ts
    

Technical Notes

  • The Pilot framework handles the translation between natural language prompts and executable code
  • Tests are cached locally after generation for faster subsequent runs
  • All AI interactions are logged in the test artifacts for debugging
  • The system falls back to traditional testing methods if the API key isn't available

For complete documentation, see: