Skip to content

feat(inspect): add --disable-animations flag to fix uiautomator dump on animated screens#118

Open
farhanlabib wants to merge 5 commits into
mobile-next:mainfrom
farhanlabib:feat/disable-animations-2
Open

feat(inspect): add --disable-animations flag to fix uiautomator dump on animated screens#118
farhanlabib wants to merge 5 commits into
mobile-next:mainfrom
farhanlabib:feat/disable-animations-2

Conversation

@farhanlabib
Copy link
Copy Markdown
Contributor

@farhanlabib farhanlabib commented May 12, 2026

Problem

npx mobilewright inspect fails with:

RpcError: no XML content found in uiautomator dump

This happens on screens that are continuously animated — a ride-searching page with a SurfaceView or TextureView that never settles. uiautomator's XML dump requires a stable layout so it always fails on these screens.

Solution

Adds --disable-animations to the inspect command and disableAnimations() enableAnimations() to the Device class. These call two new RPC methods in mobilecli — device.io.animation-scales.get and device.io.animation-scales.set — to disable the three Android global animation scales before dumping and restore the original values afterward (GET first, so we never assume the scales were 1).

Animations are always restored — in a finally block for the terminal command, on SIGINT for the browser UI, and in a finally block in tests.

Dependencies

Requires: mobile-next/mobilecli#230 — adds device.io.animation-scales.get/set RPC methods to mobilecli (Android only). This PR should be merged after that one.

Depends on: #113 (inspect --ui)

Changes

  • AnimationScales type added to @mobilewright/protocol
  • getAnimationScales() setAnimationScales() added to the MobilewrightDriver interface, implemented in driver-mobilecli and driver-mobile-use
  • Device.disableAnimations() — saves current scales, sets all to 0, returns saved scales
  • Device.enableAnimations(saved) — restores to the saved scales (defaults to 1 if none passed)
  • --disable-animations flag on inspect (terminal tree dump)
  • --disable-animations flag on inspect --ui (browser UI — disabled on start, restored on Ctrl+C)

Usage

# terminal dump on an animated screen
npx mobilewright inspect --disable-animations

# browser UI on an animated screen
npx mobilewright inspect --ui --disable-animations

In tests:

test('ride searching screen', async ({ screen, device }) => {
  const saved = await device.disableAnimations();
  try {
    await expect(screen.getByText('Finding nearby')).toBeVisible();
  } finally {
    await device.enableAnimations(saved);
  }
});

Tested

Built the mobilecli binary from mobile-next/mobilecli#230 locally, swapped it into node_modules, and confirmed device.io.animation-scales.get/set works end-to-end on a live Android device with a continuously animated ride-searching screen.

…ailure

Screenshots show what the screen looks like when a test fails, but not
why a locator didn't match. The accessibility tree carries that context —
element labels, types, visibility flags, and bounds — which is exactly
what you need to debug a failing getBy* call.

Add saveTreeOnFailure: boolean to MobilewrightConfig. When enabled, the
screen fixture calls viewTree() after a failure and attaches the result
as view-tree-on-failure (JSON) to the HTML report, next to the existing
screenshot. Opt-in, defaults to false, no impact on existing projects.
When writing tests, the main friction is not knowing the exact label,
text, or type of an element on screen. The only option was to add
screen.viewTree() inside a test and read the console output — which
requires running a test just to inspect the UI.

The inspect command solves this directly: run it with the app open on
the screen you want to target, and it prints the full accessibility tree
in the terminal with element types, labels, text, and visibility — no
test needed.

  npx mobilewright inspect

Supports --json to output raw ViewNode[] for piping to jq or saving as
a snapshot to diff between two app states:

  npx mobilewright inspect --json | jq '.[] | select(.label != null)'

Respects deviceName from config for automatic device resolution, and
accepts -d/--device to target a specific device when multiple are connected.
…plorer

The terminal tree from inspect is useful for quick lookups but hard to
navigate on complex screens with deep hierarchies. When a screen has
100+ nodes, scrolling through a flat terminal dump to find the right
element is slow.

The --ui flag opens a browser at localhost:9325 with a two-panel UI:
the left panel shows the collapsible accessibility tree, the right panel
shows the selected node's properties and ready-to-use locator code that
can be copied to the clipboard with one click.

Auto-refresh polls the device on a configurable interval (1s/2s/5s/10s)
so the tree stays live as you navigate the app — no need to re-run the
command for each screen. A manual Refresh button is always available when
auto-refresh is off.
…on animated screens

uiautomator dump fails with "no XML content found" when a screen contains
a continuously animated SurfaceView or TextureView that never settles (e.g.
a ride-searching screen). The fix is to disable the three Android system
animation scales via ADB before triggering the dump, then restore them.

Changes:
- device.disableAnimations() / enableAnimations() on the Device class
- --disable-animations flag for `inspect` (terminal) and `inspect --ui` (browser)
- animations are always restored in a finally block / on SIGINT
@farhanlabib farhanlabib force-pushed the feat/disable-animations-2 branch from 5585c73 to fbf2f07 Compare May 12, 2026 10:42
@gmegidish
Copy link
Copy Markdown
Member

@farhanlabib thanks for another great fix! :). This should go in mobilecli and not mobilewright. So it works also for mobile-mcp and cloud providers using mobilecli and mobilewright on their backend. Mind to move the code there? also, I would do a get first, so we restore what was there, and not assume we need to enable animations at the end of the code.

@farhanlabib
Copy link
Copy Markdown
Contributor Author

@farhanlabib thanks for another great fix! :). This should go in mobilecli and not mobilewright. So it works also for mobile-mcp and cloud providers using mobilecli and mobilewright on their backend. Mind to move the code there? also, I would do a get first, so we restore what was there, and not assume we need to enable animations at the end of the code.

Thanks for the guidance! I'll move this to mobilecli and open a PR there. The plan:

  1. Add device.animations.get and device.animations.set RPC methods in mobilecli (following the same pattern as device.io.orientation.get/set)
  2. get returns the current values for all three scales (window_animation_scale, transition_animation_scale, animator_duration_scale)
  3. set writes them — so callers save before disabling and restore the exact original values afterward
  4. Update this PR to call those RPC methods via the driver instead of running ADB directly from mobilewright

…direct ADB

Replaces execSync ADB calls with the new device.io.animation-scales.get/set
RPC methods from mobile-next/mobilecli#230. This means the feature works for
all mobilecli consumers (mobile-mcp, cloud providers, etc.) and not just when
adb is available on the host running mobilewright.

Changes:
- AnimationScales type added to @mobilewright/protocol
- getAnimationScales/setAnimationScales added to MobilewrightDriver interface
- Implemented in driver-mobilecli and driver-mobile-use
- Device.disableAnimations() now returns saved scales; enableAnimations() accepts them
- inspect and inspect --ui save actual values on start and restore on finish
@farhanlabib
Copy link
Copy Markdown
Contributor Author

@farhanlabib thanks for another great fix! :). This should go in mobilecli and not mobilewright. So it works also for mobile-mcp and cloud providers using mobilecli and mobilewright on their backend. Mind to move the code there? also, I would do a get first, so we restore what was there, and not assume we need to enable animations at the end of the code.

Thanks for the guidance! I'll move this to mobilecli and open a PR there. The plan:

  1. Add device.animations.get and device.animations.set RPC methods in mobilecli (following the same pattern as device.io.orientation.get/set)
  2. get returns the current values for all three scales (window_animation_scale, transition_animation_scale, animator_duration_scale)
  3. set writes them — so callers save before disabling and restore the exact original values afterward
  4. Update this PR to call those RPC methods via the driver instead of running ADB directly from mobilewright

Done — refactored in the latest commit. The execSync ADB calls are gone. Instead:

  • Added AnimationScales type to @mobilewright/protocol
  • Added getAnimationScales() / setAnimationScales() to the MobilewrightDriver interface, implemented in both driver-mobilecli and driver-mobile-use
  • Device.disableAnimations() now calls driver.getAnimationScales() first to save the real current values, then sets to 0, and returns the saved scales
  • Device.enableAnimations(saved) restores exactly what was there before
  • Same pattern in inspect and inspect --ui — GET before SET, always restore in finally/SIGINT

This PR depends on mobile-next/mobilecli#230 landing first.

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.

2 participants