Skip to content

snapshot fails with "e?.trim is not a function" when an iOS node has a numeric value (system photo picker) #3634

Closed

Description

@zaka7024

snapshot fails with e?.trim is not a function when an iOS node has a numeric value (system photo picker)

Summary

On iOS (Limrun provider), snapshot fails on any screen where an accessibility node reports a number as its value. The iOS system photo picker (PHPickerViewController) is one such screen: its "Photos" / "Collections" segmented tabs report AXValue 1 and 0 as numbers, not strings. The whole snapshot then fails, so nothing on the picker can be targeted by ref.

{
  "success": false,
  "error": {
    "code": "UNKNOWN",
    "message": "e?.trim is not a function",
    "hint": "Unexpected internal error. Retry with --debug and report the diagnostics log if it persists.",
    "details": { "dispatched": "no" }
  }
}

Versions

  • agent-device 0.21.26 and 0.21.28 (both reproduce)
  • Provider: Limrun, iOS simulator (iPhone, 402×874 pt)
  • Node 22

Steps to reproduce

  1. Use any iOS app that opens the system photo picker, e.g. an Expo app calling ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], allowsMultipleSelection: true }).
  2. agent-device connect limrun --platform ios --run-id <id>
  3. agent-device open <app> --relaunch --platform ios
  4. Tap the control that opens the photo library so the picker is on screen.
  5. agent-device snapshot -i --json --platform ios

Expected: a snapshot of the picker, listing the photo thumbnails (Image "Photo") and the picker buttons.
Actual: e?.trim is not a function, every time the picker is open.

Cause

The raw Limrun element tree for the picker includes nodes like:

{ "type": "RadioButton", "AXLabel": "Photos", "AXValue": 1 }
{ "type": "RadioButton", "AXLabel": "Collections", "AXValue": 0 }

After conversion, the snapshot nodes keep the number ("value": 1). In dist/src/react-native-overlay2.js, function E trims every node's label, value and identifier:

[i.label, i.value, i.identifier].map(e => e?.trim().toLowerCase())

e?.trim() only guards against null/undefined, so a numeric value throws. I confirmed the call site by making Number.prototype.trim log a stack trace: every throw comes from react-native-overlay2.js (E, through d → u → l, called from runtime-factory.js). With numbers coerced to strings, the snapshot succeeds and lists the picker's thumbnails and buttons correctly.

Suggested fix

Coerce value to a string when converting the Limrun iOS tree into snapshot nodes. Alternatively, guard the string helpers in the overlay pass, e.g. typeof e === 'string' ? e.trim() : e == null ? undefined : String(e).trim(). Other ?.trim() calls on node fields (e.g. label?.trim() || value?.trim() in snapshot-state.js) would benefit from the same guard.

Workaround

Load a preload in the agent-device process (the spawned daemon inherits it through the environment):

NODE_OPTIONS='--import=data:text/javascript,Object.defineProperty(Number.prototype%2C%22trim%22%2C%7Bconfigurable%3Atrue%2Cwritable%3Atrue%2Cvalue%3Afunction()%7Breturn%20String(this).trim()%7D%7D)%3B'

Related (minor)

The converted node also drops the iOS Selected trait. After a photo is tapped in the picker, the raw tree shows "traits": ["Scrollable", "Selected", "Image"], but the snapshot node has no selected field. So the selection state of picker items isn't visible in snapshots.

Activity

  1. added a commit that references this issue on Oct 15, 2026
    bbbb24c
  2. added a commit that references this issue on Oct 15, 2026
    1b41f3f
  3. thymikee commented on Oct 16, 2026

    @thymikee
    Member

    Fixed on main by #3641. It ships in the next release; agent-device@nightly (0.21.29-nightly.20261016.72) has it now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions