Visitar URL original
feat: add drag command for drag-and-drop gestures by boorad · Pull Request #2978 · mobile-dev-inc/Maestro · GitHub
Skip to content

feat: add drag command for drag-and-drop gestures - #2978

Open
boorad wants to merge 14 commits into
mobile-dev-inc:mainfrom
boorad:feat/1203-drag-drop
Open

boorad wants to merge 14 commits into
mobile-dev-inc:mainfrom
boorad:feat/1203-drag-drop

Conversation

@boorad

@boorad boorad commented Feb 4, 2026 •

Copy link
Copy Markdown

Summary

Implements a new drag command that enables drag-and-drop gestures and item reordering in mobile apps.

Closes #1203.

Supported syntax

  • Drag from/to by element id or text
  • Drag from/to by percentage coordinates
  • Drag from point with offset
  • Configurable duration

Example usage

# Drag by element text
- drag:
    from:
      text: ".*Item 3.*"
    to:
      text: ".*Item 1.*"
    duration: 3000

# Drag by percentage coordinates
- drag:
    from: "50%, 30%"
    to: "50%, 70%"

# Drag with offset
- drag:
    from: "50%, 30%"
    offset: "0%, 40%"

Platform support

Platform Status
Android ✅ Fully working
iOS + React Native ✅ Fully working
iOS + Flutter ⚠️ Known issue (see below)
Web (CDP/Selenium) ✅ Implemented

iOS + Flutter limitation

There is a known bug where element coordinates become invalid after the first drag operation in Flutter's ReorderableListView. This is a Flutter/iOS accessibility bug, not a Maestro issue. The accessibility tree returns incorrect bounds for moved elements. See DRAG_ISSUE_FLUTTER_IOS.md for detailed investigation.

Changes

  • Command layer: DragCommand data class, YAML parsing
  • Driver layer: Implementation for Android, iOS, and web drivers
  • iOS XCTest runner: New DragRouteHandler with touch event synthesis
  • Shared utilities: SeleniumGestures.kt for web driver drag logic

@boorad

boorad commented Feb 4, 2026

Copy link
Copy Markdown
Author
android.mov

@boorad

boorad commented Feb 4, 2026

Copy link
Copy Markdown
Author
ios.mov
appId: golf.spicy
---
- drag:
    from:
      id: "team-player-brad-drag"
    to:
      id: "team-unassigned-dropzone"
    duration: 2000

- drag:
    from:
      id: "team-player-eric-drag"
    to:
      id: "team-1-dropzone"
    duration: 2000

Comment thread DRAG_ISSUE_FLUTTER_IOS.md Outdated
.withOffset(CGVector(dx: start.x, dy: start.y))
```

**The problem**: This approach works if you have correct screen coordinates. But Maestro's Kotlin layer resolves coordinates by querying the accessibility hierarchy *before* calling Swift - and that hierarchy is corrupted. The Swift-side element queries (`dragByText`) also get the same bad data.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by corrupted is there an example where I can reproduce this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved this to a GitHub issue (#2984) with clearer wording — the iOS accessibility hierarchy returns incorrect (relative) bounds for elements after a Flutter ReorderableListView reorder. It's reproducible with any Flutter app using ReorderableListView + two consecutive drag operations on iOS. Android and iOS+React Native work fine.

@Fishbowler Fishbowler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

I've had a first read, and left a few comments and questions.

More generally:

  • can you remove the one-line no-op whitespace changes? They add noise to history.
  • can you add unit/integration tests?

Comment thread DRAG_ISSUE_FLUTTER_IOS.md Resolved
@boorad

boorad commented Feb 6, 2026

Copy link
Copy Markdown
Author

Thanks for the review! Addressed all feedback:

  • Whitespace changes: Reverted in cf740983
  • Unit/integration tests: Added in c5be0bd6 — YAML parsing tests in YamlCommandReaderTest and integration tests in IntegrationTest covering percentage coordinates, offsets, and element selectors
  • DRAG_ISSUE_FLUTTER_IOS.md: Moved to Drag command: Flutter iOS accessibility returns incorrect element bounds after ReorderableListView reorder #2984 and removed from the repo in 32807b4f
  • YamlDrag complexity: Simplified in 92b01bac
  • Dead dragByText code + commented-out Orchestra code: Removed in 821e326b
  • Maestro.kt tap() refactor: Reverted in cf740983 (was troubleshooting residue from the Flutter/iOS bug)

All inline comments have individual replies as well.

@boorad

boorad commented Feb 11, 2026 •

Copy link
Copy Markdown
Author

@Fishbowler @amanjeetsingh150 re-review, plz

Also, given all these conflicts, you should move the binaries to ghcr.io or release artifacts or something.

@hackertronix

Copy link
Copy Markdown

@Fishbowler @amanjeetsingh150 do you have any ETA on when this might land. Happy to help if there's something blocking this from merging!

@Fishbowler
Fishbowler force-pushed the feat/1203-drag-drop branch from 6b12919 to 2bf3b3a Compare July 2, 2026 15:14
@Fishbowler

Copy link
Copy Markdown
Contributor

Rebased

@Fishbowler

Copy link
Copy Markdown
Contributor

Added the changes from mobile-dev-inc/demo_app#10

@hackertronix

Copy link
Copy Markdown
gif

@Fishbowler

Copy link
Copy Markdown
Contributor

I'm struggling with the fact that I can't make this work in a flutter app on iOS 🤔

It does seem like #2984 is gonna cause problems for folks, and it certainly looks like a Flutter upstream problem.

I was gonna investigate if we can defend against it, but it's not a duplicate - Maestro can't help folks with this. I'll look at a different demo_app impl instead for now.

@Fishbowler

Copy link
Copy Markdown
Contributor

I think I've identified a small gap in the implementation. It works for a single move in a Flutter ReorderableListView (but not 2, as you've documented). It also works for a basic Draggable. If a developer has a LongPressDraggable, where a pause-wait is required before moving, then the iOS implementation won't activate the element.

Unsure whether this needs a longer default wait, or a configurable initial pause 🤔

@Fishbowler

Copy link
Copy Markdown
Contributor

Right, I've added a web test, and I've messed with the other e2e tests. The existing ReorderableListView is reduced to a single reordering. I've also added a different widget screen with the simple Draggable that works.

Let me know what you think about the LongPressDraggable problem - I've not added a test for it, but it's a 1 word change in the flutter screen I've added.

@AfzalivE

AfzalivE commented Jul 14, 2026 •

Copy link
Copy Markdown

Hi @Fishbowler, I would like to look into the accessibility-related iOS blocker if that's okay. I have some experience digging into Flutter internals from a while back (flutter/flutter#45075). Is it still an issue or were you able to figure out a different demo_app impl?

If a developer has a LongPressDraggable, where a pause-wait is required before moving, then the iOS implementation won't activate the element.

Or is this the bigger blocker?

boorad and others added 11 commits July 17, 2026 12:09
Implements a new 'drag' command that enables drag-and-drop gestures and
item reordering in mobile apps.

Supported syntax:
- drag from/to by element id or text
- drag from/to by percentage coordinates
- drag from point with offset
- configurable duration

Status:
- Android: fully working
- iOS + React Native: fully working
- iOS + Flutter: known bug where element coordinates become invalid
  after first drag (Flutter accessibility issue, not Maestro)

See DRAG_ISSUE_FLUTTER_IOS.md for detailed investigation of the
Flutter+iOS issue.
- Remove unused 'steps' variable in AndroidDriver.drag()
- Extract duplicate drag implementation from WebDriver and CdpWebDriver
  to shared SeleniumGestures.performDrag() utility
- Add DRAG_LONG_PRESS_DURATION_MS constant (1000ms) for clarity
- Rename drag_test.yaml to drag_text.yaml (element selector based)
- Add drag_pcts.yaml (percentage coordinate based)
- Add clearState to both tests for consistent starting state
- Add warning about percentage-based tests being device-specific
@Fishbowler
Fishbowler force-pushed the feat/1203-drag-drop branch from 5ae8cf5 to ffca37b Compare July 17, 2026 11:09
@Fishbowler

Copy link
Copy Markdown
Contributor

Hey @AfzalivE. Thanks for the offer! I've done a clean rebase on main, so it's fresh to work on.

It's a boring answer, but I think both?

  • A working LongPressDraggable feels important - that's a pretty common implementation of drag-n-drop, right?
  • Similarly, it not being usable on Flutter feels inconsistent with how we've historically run the project - that the underlying framework shouldn't matter. But I'm unsure if this is solvable inside of Maestro?

@AfzalivE

AfzalivE commented Jul 17, 2026 •

Copy link
Copy Markdown

Hey @AfzalivE. Thanks for the offer! I've done a clean rebase on main, so it's fresh to work on.

It's a boring answer, but I think both?

  • A working LongPressDraggable feels important - that's a pretty common implementation of drag-n-drop, right?
  • Similarly, it not being usable on Flutter feels inconsistent with how we've historically run the project - that the underlying framework shouldn't matter. But I'm unsure if this is solvable inside of Maestro?

I've got good news @Fishbowler

  1. I have a commit which adds pressDuration and a LongPressDraggable test that works on iOS. Just have to make some changes to actually use pressDuration on Android.

  2. I have a fix for Flutter that I'll create a PR for against upstream once I've cleaned it up. They have been tracking this issue for a while in ReorderebleListView will move the Semantics focus to the incorrect position when reordering. flutter/flutter#100946. It only happens when a Semantics widget has container: true (Card has that).

@AfzalivE

Copy link
Copy Markdown

Created flutter/flutter#189686

For the pressDuration change, how do you prefer I create the PR. I don't think I can use boorad's branch as base.

@Fishbowler

Copy link
Copy Markdown
Contributor

Create a new PR. Reference this one. We can close this as superseded.

@AfzalivE

Copy link
Copy Markdown

Created #3453

Thank you!

kirillzyusko added a commit to kirillzyusko/react-native-teleport that referenced this pull request Sep 4, 2026
## 📜 Description

Refactors the Maestro E2E suite to reduce repeated platform checks and
screenshot assertion boilerplate.

This introduces reusable flows for screenshot assertions and video
recording. Gesture actions remain inline because Maestro cannot safely
parameterize typed `swipe` fields such as coordinates, direction, and
duration.

Existing drag-related scenarios now run on web using a press fallback
instead of skipping the entire test.

## 💡 Motivation and Context

Several E2E flows repeated the same screenshot workflow:

1. Capture a screenshot.
2. Compare it with the platform baseline.
3. Apply a configured difference threshold.
4. Assert that the comparison passed.

Video recording also required repeated platform checks because Maestro
does not currently support `startRecording` and `stopRecording` on web.

Centralizing these operations makes the tests shorter, keeps
platform-specific behavior consistent, and provides one place to update
the implementation when Maestro adds the missing web capabilities.

Related Maestro issues:

- [Web video recording
support](mobile-dev-inc/Maestro#3468)
- [Web pointer drag
support](mobile-dev-inc/Maestro#2978)

## 📢 Changelog

### E2E

- Added a reusable screenshot assertion flow that captures, compares,
and asserts screenshots.
- Added support for a custom screenshot difference threshold, defaulting
to `0.3`.
- Migrated 32 repeated screenshot assertion sequences across eight E2E
flows.
- Added reusable start and stop recording flows.
- Updated the Instagram Reels test to use the recording flows.
- Kept swipe and drag actions inline because Maestro cannot parameterize
their typed fields safely.

### Web

- Video recording flows act as no-ops until Maestro supports recording
on web.
- Bottom-sheet and pressable drag scenarios now use a press fallback
instead of skipping the complete test.
- Shared setup and final assertions now run on web as well as native
platforms.

### iOS

- Video recording continues to use Maestro’s native `startRecording` and
`stopRecording` commands.
- Drag scenarios continue to use their original native swipe behavior.

### Android

- Video recording continues to use Maestro’s native `startRecording` and
`stopRecording` commands.
- Drag scenarios continue to use their original native swipe behavior.

## 🤔 How Has This Been Tested?

- Validated all Maestro flow and subflow syntax.
- Validated suite parsing using the same `maestro test ... e2e/flows/*`
structure used by CI.
- Verified that custom screenshot thresholds are preserved where
required.
- Web and Android automated E2E jobs pass in CI.
- The iOS automated E2E job is still running at the time of writing.
- TypeScript compilation, linting, unit tests, package builds,
spellcheck, and package-size checks pass.

## 📸 Screenshots (if appropriate):

<img width="778" height="308" alt="image"
src="https://github.com/user-attachments/assets/fc895a4f-b9de-4013-9d08-f391d298c111"
/>

## 📝 Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed (not applicable—there are no library API changes)
@Fishbowler Fishbowler added the awaiting maestro 3.0 Driver work: on hold until after Maestro 3.0 label Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting maestro 3.0 Driver work: on hold until after Maestro 3.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Hold and swipe/drag to reorder items

5 participants