Skip to content

Conversation

@redbasecap
Copy link

@redbasecap redbasecap commented Dec 22, 2025

Add consistent error handling pattern to AndroidRobot, IosRobot, and MobileDevice classes using new shared utility functions.

Changes:

  • Add extractCommandError() to extract user-friendly messages from child process errors (stdout/stderr)
  • Add withActionableError() wrapper for consistent error handling that converts all errors to ActionableError with context
  • Update all Robot interface method implementations to use the new error handling utilities
  • Improve error messages with contextual information (e.g., device coordinates, package names, file paths)

This ensures all device operations provide clear, actionable error messages regardless of the underlying platform (Android/iOS/simulator).

Files changed:

  • src/robot.ts: Add error handling utilities
  • src/android.ts: Apply consistent error handling to all methods
  • src/ios.ts: Apply consistent error handling to all methods
  • src/mobile-device.ts: Apply consistent error handling to all methods

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced error messages across device operations including app launch, installation, uninstallation, screenshots, orientation control, and UI element queries with clearer, more actionable feedback for troubleshooting
  • Refactor

    • Consolidated error handling across Android, iOS, and mobile device operations for consistent, user-friendly error messaging

✏️ Tip: You can customize this high-level summary in your review settings.

Add consistent error handling pattern to AndroidRobot, IosRobot, and
MobileDevice classes using new shared utility functions.

Changes:
- Add extractCommandError() to extract user-friendly messages from
  child process errors (stdout/stderr)
- Add withActionableError() wrapper for consistent error handling that converts all errors to ActionableError with context
- Update all Robot interface method implementations to use the new
  error handling utilities
- Improve error messages with contextual information (e.g., device
  coordinates, package names, file paths)

This ensures all device operations provide clear, actionable error
messages regardless of the underlying platform (Android/iOS/simulator).

Files changed:
- src/robot.ts: Add error handling utilities
- src/android.ts: Apply consistent error handling to all methods
- src/ios.ts: Apply consistent error handling to all methods
- src/mobile-device.ts: Apply consistent error handling to all methods
@coderabbitai
Copy link

coderabbitai bot commented Dec 22, 2025

Walkthrough

This PR introduces standardized error handling across device control modules by implementing a withActionableError wrapper function and error message extraction utility. Multiple methods in android.ts, ios.ts, and mobile-device.ts now consistently wrap operations to convert errors into actionable, user-facing messages.

Changes

Cohort / File(s) Summary
Error handling utilities
src/robot.ts
Added extractCommandError() to derive user-friendly messages from stdout/stderr; added withActionableError() wrapper to convert operations into standardized ActionableError with context; updated ActionableError constructor to assign this.name.
Android device methods
src/android.ts
Imported withActionableError and wrapped multiple methods (getScreenSize, listApps, launchApp, terminateApp, installApp, uninstallApp, openUrl, tap, longPress, swipe variants, getScreenshot, getElementsOnScreen, getUiAutomatorXml, setOrientation, getOrientation) to return/throw ActionableError with descriptive failure messages.
iOS device methods
src/ios.ts
Imported withActionableError and wrapped listApps, launchApp, terminateApp, and installApp methods with standardized error handling and tailored failure messages.
Mobile device abstraction
src/mobile-device.ts
Imported withActionableError and wrapped core public methods (getScreenSize, swipe, swipeFromCoordinate, getScreenshot, listApps, launchApp, terminateApp, installApp, uninstallApp, openUrl, sendKeys, pressButton, tap, longPress, getElementsOnScreen, setOrientation, getOrientation) to propagate actionable errors with context-specific messages.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: unifying error handling across Robot implementations by introducing shared utilities and wrapping all methods with consistent error handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mobile-device.ts (1)

85-116: Inconsistent error handling compared to other methods.

Unlike other methods in this file, the swipe method doesn't wrap the runCommand call (line 115) in withActionableError. This creates inconsistency with the rest of the implementation where all command executions are wrapped for uniform error handling.

🔎 Proposed fix
 	public async swipe(direction: SwipeDirection): Promise<void> {
-		const screenSize = await this.getScreenSize();
-		const centerX = Math.floor(screenSize.width / 2);
-		const centerY = Math.floor(screenSize.height / 2);
-		const distance = 400; // Default distance in pixels
-
-		let startX = centerX;
-		let startY = centerY;
-		let endX = centerX;
-		let endY = centerY;
-
-		switch (direction) {
-			case "up":
-				startY = centerY + distance / 2;
-				endY = centerY - distance / 2;
-				break;
-			case "down":
-				startY = centerY - distance / 2;
-				endY = centerY + distance / 2;
-				break;
-			case "left":
-				startX = centerX + distance / 2;
-				endX = centerX - distance / 2;
-				break;
-			case "right":
-				startX = centerX - distance / 2;
-				endX = centerX + distance / 2;
-				break;
-		}
-
-		this.runCommand(["io", "swipe", `${startX},${startY},${endX},${endY}`]);
+		return withActionableError(async () => {
+			const screenSize = await this.getScreenSize();
+			const centerX = Math.floor(screenSize.width / 2);
+			const centerY = Math.floor(screenSize.height / 2);
+			const distance = 400; // Default distance in pixels
+
+			let startX = centerX;
+			let startY = centerY;
+			let endX = centerX;
+			let endY = centerY;
+
+			switch (direction) {
+				case "up":
+					startY = centerY + distance / 2;
+					endY = centerY - distance / 2;
+					break;
+				case "down":
+					startY = centerY - distance / 2;
+					endY = centerY + distance / 2;
+					break;
+				case "left":
+					startX = centerX + distance / 2;
+					endX = centerX - distance / 2;
+					break;
+				case "right":
+					startX = centerX - distance / 2;
+					endX = centerX + distance / 2;
+					break;
+			}
+
+			this.runCommand(["io", "swipe", `${startX},${startY},${endX},${endY}`]);
+		}, `Failed to swipe ${direction}`);
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef1319 and abe6850.

📒 Files selected for processing (4)
  • src/android.ts
  • src/ios.ts
  • src/mobile-device.ts
  • src/robot.ts
🧰 Additional context used
🧬 Code graph analysis (4)
src/robot.ts (1)
src/logger.ts (1)
  • error (19-21)
src/ios.ts (1)
src/robot.ts (1)
  • withActionableError (61-71)
src/mobile-device.ts (1)
src/robot.ts (5)
  • withActionableError (61-71)
  • InstalledApp (10-13)
  • Button (17-17)
  • ScreenElement (26-37)
  • Orientation (73-73)
src/android.ts (1)
src/robot.ts (4)
  • withActionableError (61-71)
  • InstalledApp (10-13)
  • SwipeDirection (15-15)
  • Orientation (73-73)
🔇 Additional comments (7)
src/robot.ts (2)

42-43: LGTM! Good practice for custom error classes.

Setting the name property helps with error identification and debugging.


46-71: LGTM! Solid error handling implementation.

The utilities correctly extract error messages from child process errors and provide consistent error wrapping with contextual information.

src/ios.ts (1)

128-139: LGTM! Proper async/await usage.

The listApps method correctly awaits the async operation within withActionableError.

src/mobile-device.ts (2)

76-82: LGTM! Proper error wrapping.

The method correctly wraps the synchronous operations in withActionableError.


119-140: LGTM! Consistent error handling across all methods.

All these methods correctly apply withActionableError with appropriate contextual error messages.

Also applies to: 144-147, 151-157, 161-164, 168-171, 175-178, 182-185, 189-192, 196-199, 203-206, 210-213, 223-226, 230-242, 246-249, 253-256

src/android.ts (2)

103-116: LGTM! Good error handling and validation.

The method wraps the screen size retrieval with proper error handling and includes validation for missing output.


120-133: LGTM! Consistent error handling across all Android operations.

All methods correctly apply withActionableError with contextual error messages. The implementation maintains consistency with the iOS and mobile-device implementations.

Also applies to: 146-149, 162-194, 198-235, 302-317, 360-365, 369-372, 376-379, 383-386, 390-393, 447-450, 454-458, 468-474, 478-481

Comment on lines +144 to +147
await withActionableError(
() => { this.ios("launch", packageName); },
`Failed to launch app "${packageName}". Please make sure it exists`
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing await causes error handling to fail.

The this.ios() method is async (returns Promise<string> as defined on line 94), but it's called without await inside the operation. This means:

  1. The operation completes immediately without waiting for the command to finish
  2. Errors from the command won't be caught by withActionableError
  3. The method may return before the actual operation completes or fails
🔎 Proposed fix
-		await withActionableError(
-			() => { this.ios("launch", packageName); },
-			`Failed to launch app "${packageName}". Please make sure it exists`
-		);
+		await withActionableError(
+			async () => { await this.ios("launch", packageName); },
+			`Failed to launch app "${packageName}". Please make sure it exists`
+		);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await withActionableError(
() => { this.ios("launch", packageName); },
`Failed to launch app "${packageName}". Please make sure it exists`
);
await withActionableError(
async () => { await this.ios("launch", packageName); },
`Failed to launch app "${packageName}". Please make sure it exists`
);
🤖 Prompt for AI Agents
In src/ios.ts around lines 144-147, the arrow callback passed to
withActionableError uses a block but does not return or await the async
this.ios(...) call, so the operation completes immediately and errors escape
handling; change the callback to return the Promise (either by removing the
braces for an implicit return: () => this.ios("launch", packageName) or
explicitly return/await it: async () => await this.ios("launch", packageName))
so the Promise is awaited and errors are caught by withActionableError.

Comment on lines +152 to +155
await withActionableError(
() => { this.ios("kill", packageName); },
`Failed to terminate app "${packageName}"`
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing await causes error handling to fail.

Same issue as launchApp - this.ios() is async but not awaited, breaking error handling.

🔎 Proposed fix
-		await withActionableError(
-			() => { this.ios("kill", packageName); },
-			`Failed to terminate app "${packageName}"`
-		);
+		await withActionableError(
+			async () => { await this.ios("kill", packageName); },
+			`Failed to terminate app "${packageName}"`
+		);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await withActionableError(
() => { this.ios("kill", packageName); },
`Failed to terminate app "${packageName}"`
);
await withActionableError(
async () => { await this.ios("kill", packageName); },
`Failed to terminate app "${packageName}"`
);
🤖 Prompt for AI Agents
In src/ios.ts around lines 152 to 155, the call to this.ios("kill", packageName)
is passed to withActionableError without awaiting it, so thrown/rejected errors
bypass the wrapper; change the lambda to return/await the async call (i.e., make
it an async function that awaits this.ios(...)) so withActionableError receives
the Promise and can handle errors correctly.

Comment on lines +160 to +163
await withActionableError(
() => { this.ios("install", "--path", path); },
`Failed to install app from "${path}"`
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing await causes error handling to fail.

Same issue as launchApp - this.ios() is async but not awaited, breaking error handling.

🔎 Proposed fix
-		await withActionableError(
-			() => { this.ios("install", "--path", path); },
-			`Failed to install app from "${path}"`
-		);
+		await withActionableError(
+			async () => { await this.ios("install", "--path", path); },
+			`Failed to install app from "${path}"`
+		);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await withActionableError(
() => { this.ios("install", "--path", path); },
`Failed to install app from "${path}"`
);
await withActionableError(
async () => { await this.ios("install", "--path", path); },
`Failed to install app from "${path}"`
);
🤖 Prompt for AI Agents
In src/ios.ts around lines 160-163, the inline function passed to
withActionableError calls this.ios(...) but does not return/await it, so errors
won't propagate; change the callback to return the promise (e.g., use () =>
this.ios("install", "--path", path) or async () => await this.ios("install",
"--path", path)) so the async call is awaited and withActionableError can catch
failures.

Comment on lines +168 to +171
await withActionableError(
() => { this.ios("uninstall", "--bundleid", bundleId); },
`Failed to uninstall app "${bundleId}"`
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing await causes error handling to fail.

Same issue as launchApp - this.ios() is async but not awaited, breaking error handling.

🔎 Proposed fix
-		await withActionableError(
-			() => { this.ios("uninstall", "--bundleid", bundleId); },
-			`Failed to uninstall app "${bundleId}"`
-		);
+		await withActionableError(
+			async () => { await this.ios("uninstall", "--bundleid", bundleId); },
+			`Failed to uninstall app "${bundleId}"`
+		);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await withActionableError(
() => { this.ios("uninstall", "--bundleid", bundleId); },
`Failed to uninstall app "${bundleId}"`
);
await withActionableError(
async () => { await this.ios("uninstall", "--bundleid", bundleId); },
`Failed to uninstall app "${bundleId}"`
);
🤖 Prompt for AI Agents
In src/ios.ts around lines 168 to 171, the call to this.ios(...) is not awaited
inside the withActionableError wrapper so thrown errors bypass the handler;
change the lambda to return and await the promise (i.e. make the arrow function
directly return this.ios(...) and add await before withActionableError), so the
async operation is awaited and errors are propagated into withActionableError
for proper handling.

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.

1 participant