Repository navigation
snapshot fails with "e?.trim is not a function" when an iOS node has a numeric value (system photo picker) #3634
Description
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.26and0.21.28(both reproduce) - Provider: Limrun, iOS simulator (iPhone, 402×874 pt)
- Node 22
Steps to reproduce
- Use any iOS app that opens the system photo picker, e.g. an Expo app calling
ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], allowsMultipleSelection: true }). agent-device connect limrun --platform ios --run-id <id>agent-device open <app> --relaunch --platform ios- Tap the control that opens the photo library so the picker is on screen.
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
fix(limrun): normalize variant iOS scalar attributes into snapshot text
fix(limrun): normalize variant iOS scalar attributes into snapshot te…
thymikee commentedon Oct 16, 2026
Fixed on main by #3641. It ships in the next release;
agent-device@nightly(0.21.29-nightly.20261016.72) has it now.