formatNativeTapResult function
Writes the stdout tokens / stderr lines for result and returns the exit code.
Implementation
int formatNativeTapResult(NativeTapResult result) {
switch (result) {
case NativeTapAndroid(:final x, :final y, :final text):
final textSuffix = text != null ? ' TEXT="${_escapeQuoted(text)}"' : '';
stdout.writeln('NATIVE_TAPPED=android X=$x Y=$y$textSuffix');
return 0;
case NativeTapIosSimulator(:final x, :final y, :final text):
final textSuffix = text != null ? ' TEXT="${_escapeQuoted(text)}"' : '';
stdout.writeln('NATIVE_TAPPED=ios-simulator X=$x Y=$y$textSuffix');
return 0;
case NativeTapIosSimulatorFallback(:final x, :final y, :final reason, :final tapResult):
stderr.writeln(
'WARNING: iOS simulator HID tap unavailable ($reason); fell back to in-process tap '
'(UIApplication.sendEvent), which cannot reach SpringBoard system dialogs.',
);
final tapExitCode = formatTapResult(tapResult);
if (tapExitCode != 0) return tapExitCode;
stdout.writeln('NATIVE_TAPPED=ios-simulator X=$x Y=$y');
return 0;
case NativeTapIosSimulatorOutOfBounds(:final message):
stderr.writeln('ERROR: $message');
return 1;
case NativeTapIosSimulatorOrientationUnknown(:final message):
stderr.writeln('ERROR: $message');
return 1;
case NativeTapIosSimulatorFailed(:final message):
stderr.writeln('ERROR: $message');
return 1;
case NativeTapIosSimulatorAccessibilityUnavailable(:final reason):
stderr.writeln(
'ERROR: native-tap --text needs the iOS simulator accessibility API, which is not available ($reason). '
'Tap by coordinates with --at x,y instead.',
);
return 1;
case NativeTapIosSimulatorTreeUnreadable(:final reason):
stderr.writeln('ERROR: native-tap --text could not read the iOS simulator accessibility tree ($reason).');
return 1;
case NativeTapIosSimulatorTapUnavailable(:final reason):
stderr.writeln('ERROR: native-tap --text could not tap on the iOS simulator ($reason).');
return 1;
case NativeTapIosSimulatorFramesUnmapped(:final query, :final orientation, :final labels):
stderr.writeln(
'ERROR: native-tap --text could not place the native elements on the iOS simulator screen '
'($orientation), so "$query" can\'t be tapped by label. Rotate the simulator to portrait, or tap with '
'--at x,y. Labels: ${_labelList(labels)}',
);
return 1;
case NativeTapNoSession():
stderr.writeln('ERROR: No active fdb session found. Run fdb launch first.');
return 1;
case NativeTapPhysicalIosUnsupported(:final x, :final y):
final at = _atArg(x, y);
stderr.writeln(
'ERROR: native-tap is not yet supported on physical iOS devices.\n'
' Use `fdb tap --at $at` instead — it performs in-process tap\n'
' injection via fdb_helper, which reaches UIAlertController and other\n'
' in-app native overlays on physical iOS devices.\n'
'\n'
' Why: out-of-process tap injection on physical iOS requires\n'
' WebDriverAgent (a signed XCUITest runner installed on the device).\n'
' Tracking implementation in beads issue fdb-6sz.',
);
return 1;
case NativeTapMacosUnsupported(:final x, :final y):
final instead = x != null && y != null ? 'fdb tap --at ${_atArg(x, y)}' : 'fdb tap --text <label>';
stderr.writeln(
'ERROR: native-tap is not supported on macOS.\n'
' Use `$instead` instead — it performs in-process tap injection\n'
' via fdb_helper and does not require Accessibility permission.\n'
'\n'
' Why: cross-process tap injection on macOS requires Accessibility\n'
' permission, which is only grantable to signed .app bundles. Homebrew\n'
' CLIs are unsigned and cannot be added to the Accessibility list.',
);
return 1;
case NativeTapPlatformUnsupported(:final platform):
stderr.writeln('ERROR: native-tap is not supported on platform "$platform".');
return 1;
case NativeTapAdbFailed(:final details):
stderr.writeln('ERROR: adb input tap failed: $details');
return 1;
case NativeTapInputInjectionBlocked(:final details):
stderr.writeln(
'ERROR: Android blocked input injection (INJECT_EVENTS). Enable it in Developer options: '
'Xiaomi/HyperOS "USB debugging (Security settings)", OPPO/OnePlus/Realme "Disable permission monitoring".\n'
' adb said: ${_firstLine(details)}',
);
return 1;
case NativeTapNoMatch(:final query, :final visibleLabels):
stderr.writeln('ERROR: No native element matching "$query". Visible labels: ${_labelList(visibleLabels)}');
return 1;
case NativeTapAmbiguous(:final query, :final candidates):
final lines = [
for (var i = 0; i < candidates.length; i++)
' [$i] "${candidates[i].label}" at ${candidates[i].x},${candidates[i].y}',
];
stderr.writeln(
'ERROR: Found ${candidates.length} native elements matching "$query". '
'Use --index to specify which one (0-based):\n'
'${lines.join('\n')}',
);
return 1;
case NativeTapIndexOutOfRange(:final query, :final index, :final count):
stderr.writeln(
'ERROR: --index $index is out of range: found $count native element${count == 1 ? '' : 's'} '
'matching "$query" (0-based).',
);
return 1;
case NativeTapUiDumpFailed(:final reason):
final idle = reason.contains('idle state');
stderr.writeln(
'ERROR: Could not read the Android UI hierarchy (uiautomator dump): $reason'
'${idle ? '\n uiautomator needs the screen to stop animating. Retry, or tap by coordinates with --at x,y.' : ''}',
);
return 1;
case NativeTapDevicePixelRatioUnavailable(:final reason):
stderr.writeln('ERROR: Could not determine the device pixel ratio for --logical: $reason');
return 1;
case NativeTapAdbExecutionFailed(:final error):
stderr.writeln(
'ERROR: Failed to run adb: $error\n'
' Install adb: https://developer.android.com/studio/command-line/adb',
);
return 1;
}
}