-
Notifications
You must be signed in to change notification settings - Fork 63
Recording and playback initial version: Supports simple recording and basic playback capabilities. #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces an initial version of recording and playback functionality for mobile UI automation. It adds the ability to record user interactions, save them as scripts, generate code in multiple formats (Appium Python/JS, UIAutomator2, XCUITest), and replay recorded actions.
Key Changes:
- New
/api/recordendpoints for saving, listing, retrieving, generating, and executing recorded scripts - Support for recording tap, input, swipe, back, and home actions with element selectors and coordinates
- Script generation in multiple automation framework formats
- Script execution engine with support for both UIAutomator2 and ADB-based drivers
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 23 comments.
Show a summary per file
| File | Description |
|---|---|
| uiautodev/router/record.py | New recording API router with endpoints for script management and playback, including script generators for multiple frameworks |
| uiautodev/model.py | Added data models for recording functionality (Selector, RecordEvent, RecordScript, SaveScriptRequest, SaveScriptResponse) |
| uiautodev/driver/android/adb_driver.py | Enhanced app_launch method with stop_first parameter and monkey command for more reliable app launching |
| uiautodev/command_types.py | Changed default value of AppLaunchRequest.stop from False to True for cleaner app launches |
| uiautodev/command_proxy.py | Updated app_launch function to support new stop_first parameter with backward compatibility |
| uiautodev/app.py | Registered record router and added DELETE/PUT methods to CORS middleware |
Comments suppressed due to low confidence (1)
uiautodev/driver/android/adb_driver.py:170
- This statement is unreachable.
try:
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
uiautodev/router/record.py
Outdated
| ) | ||
|
|
||
| # Wait a bit between actions | ||
| time.sleep(0.5) |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic number detected: The sleep duration 0.5 is a magic number. Consider defining it as a named constant (e.g., ACTION_DELAY = 0.5) to improve code readability and maintainability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
uiautodev/command_types.py
Outdated
| class AppLaunchRequest(BaseModel): | ||
| package: str | ||
| stop: bool = False | ||
| stop: bool = True # Default to True: stop app before launch for clean start |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking change in default behavior: The default value of stop has been changed from False to True. This means that existing code calling AppLaunchRequest without explicitly setting stop will now stop the app before launching, which is a behavioral change that could break existing workflows. Consider documenting this change clearly or using a different parameter name to avoid breaking existing code.
| stop: bool = True # Default to True: stop app before launch for clean start | |
| stop: bool = False # Default to False: do not stop app before launch (backward compatible) |
uiautodev/router/record.py
Outdated
| lines.append(f' await driver.$("//*[@text=\'{event.selector.text}\']").click();') | ||
| elif event.action == "input": | ||
| if event.selector and event.selector.id: | ||
| lines.append(f' await driver.$("#{event.selector.id}").setValue("{event.value or ""}");') |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential code injection vulnerability: event.value is directly interpolated into JavaScript code without escaping. Input values containing double quotes or special characters could break the generated code. Consider using proper escaping for the generated string.
| lines.append(f' await driver.$("#{event.selector.id}").setValue("{event.value or ""}");') | |
| lines.append(f' await driver.$("#{event.selector.id}").setValue({json.dumps(event.value or "")});') |
uiautodev/router/record.py
Outdated
| if event.x is not None and event.y is not None: | ||
| print(f"[execute_script] [COORDINATE] Tapping at recorded coordinates ({event.x}, {event.y})") | ||
| logger.info(f"[COORDINATE] Tapping at recorded coordinates ({event.x}, {event.y})") | ||
| driver.tap(int(event.x), int(event.y)) | ||
| else: | ||
| error_msg = "No selector or coordinates provided for tap action" | ||
| print(f"[execute_script] {error_msg}") | ||
| logger.error(error_msg) | ||
| return ScriptExecutionResult( | ||
| step=step_index + 1, | ||
| action=event.action, | ||
| success=False, | ||
| message=error_msg, | ||
| error=error_msg | ||
| ) |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate code block detected. Lines 545-559 are identical to lines 528-544. This unreachable code will never execute because all code paths in the preceding block either return early or complete the tap action. This duplicated block should be removed.
| if event.x is not None and event.y is not None: | |
| print(f"[execute_script] [COORDINATE] Tapping at recorded coordinates ({event.x}, {event.y})") | |
| logger.info(f"[COORDINATE] Tapping at recorded coordinates ({event.x}, {event.y})") | |
| driver.tap(int(event.x), int(event.y)) | |
| else: | |
| error_msg = "No selector or coordinates provided for tap action" | |
| print(f"[execute_script] {error_msg}") | |
| logger.error(error_msg) | |
| return ScriptExecutionResult( | |
| step=step_index + 1, | |
| action=event.action, | |
| success=False, | |
| message=error_msg, | |
| error=error_msg | |
| ) |
uiautodev/router/record.py
Outdated
| lines.append(f' await driver.$("#{event.selector.id}").click();') | ||
| elif event.selector and event.selector.text: | ||
| lines.append(f' await driver.$("//*[@text=\'{event.selector.text}\']").click();') |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential code injection vulnerability: event.selector.id is directly interpolated into JavaScript selector without escaping. Special characters in the ID could break the generated code. Consider using proper escaping for the generated string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
uiautodev/router/record.py
Outdated
| lines.append(f' app.buttons["{event.selector.id}"].tap()') | ||
| elif event.selector and event.selector.text: | ||
| lines.append(f' app.staticTexts["{event.selector.text}"].tap()') | ||
| elif event.action == "input": | ||
| if event.selector and event.selector.id: | ||
| lines.append(f' app.textFields["{event.selector.id}"].typeText("{event.value or ""}")') |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential code injection vulnerability: event.selector.id, event.selector.text, and event.value are directly interpolated into Swift code without escaping. Special characters in these values could break the generated code. Consider using proper escaping for the generated strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
|
|
||
| import json | ||
| import logging | ||
| import os |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'os' is not used.
| import os |
|
|
||
| from uiautodev.model import RecordEvent, RecordScript, SaveScriptRequest, SaveScriptResponse | ||
| from uiautodev.provider import AndroidProvider | ||
| from uiautodev.command_proxy import send_command |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'send_command' is not used.
| from uiautodev.command_proxy import send_command |
| from uiautodev.model import RecordEvent, RecordScript, SaveScriptRequest, SaveScriptResponse | ||
| from uiautodev.provider import AndroidProvider | ||
| from uiautodev.command_proxy import send_command | ||
| from uiautodev.command_types import Command, TapRequest, SendKeysRequest, By, FindElementRequest |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'By' is not used.
Import of 'Command' is not used.
Import of 'TapRequest' is not used.
Import of 'SendKeysRequest' is not used.
Import of 'FindElementRequest' is not used.
| from uiautodev.command_types import Command, TapRequest, SendKeysRequest, By, FindElementRequest | |
| # Removed unused imports: Command, TapRequest, SendKeysRequest, By, FindElementRequest |
uiautodev/command_proxy.py
Outdated
| # Driver supports stop_first parameter | ||
| driver.app_launch(params.package, stop_first=stop_first) | ||
| return | ||
| except (TypeError, AttributeError): |
Copilot
AI
Nov 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'except' clause does nothing but pass and there is no explanatory comment.
| except (TypeError, AttributeError): | |
| except (TypeError, AttributeError): | |
| # If signature inspection fails, fallback to manual stop/launch below. |
| total: int | ||
|
|
||
|
|
||
| class ScriptGenerator: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
代码生成没必要放到后端,放前端就可以。这样后面升级也方便
| ) | ||
|
|
||
| def app_launch(self, package: str): | ||
| def app_launch(self, package: str, stop_first: bool = True): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stop_first这里不要加,分开成两个操作。 app_stop() 和 app_launch(). 因为除了android驱动还有ios,鸿蒙驱动。修改一个就好把其他的两个也一起改了。不划算
录制回放初版:支持简单的录制和基本的回放能力