add reusable Selector support and flexible argument passing#1167
add reusable Selector support and flexible argument passing#1167Zeros2619 wants to merge 2 commits into
Conversation
Zeros2619
commented
Feb 26, 2026
- Introduce reusable Selector pattern to replace inline element queries
- Support two ways to use Selector:
- Positional argument: d(Selector(...))
- Keyword argument: d(selector=Selector(...))
- Improve code reusability and maintainability for element selection logic
1. Introduce reusable Selector pattern to replace inline element queries 2. Support two ways to use Selector: - Positional argument: d(Selector(...)) - Keyword argument: d(selector=Selector(...)) 3. Improve code reusability and maintainability for element selection logic
|
Positional argument: d(Selector(...)). 这个要不去掉好了,很容易传错参数。另外给 selector参数加个类型断言 Optional[Selector] |
There was a problem hiding this comment.
Pull request overview
Adds a reusable Selector pattern to the uiautomator2 Python client API so selectors can be constructed once and passed around (e.g., d(sel) or d(selector=sel)), reducing repeated inline element queries.
Changes:
- Extend
SelectorandDevice.__call__to accept an existingSelectorvia positional arg orselector=kwarg, and propagate this through related APIs (child/sibling, relative navigation,drag_to). - Refactor selector cloning/copying logic (introduce internal deep-copy helper and
__deepcopy__support). - Update docs and add demo tests demonstrating the new usage.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
uiautomator2/_selector.py |
Adds selector reuse/copy semantics and updates several APIs to accept Selector as an argument. |
uiautomator2/__init__.py |
Broadens Device.__call__ to forward positional/keyword arguments into Selector(...). |
demo_tests/test_selector.py |
Demonstrates new selector reuse style and usage in relative selection calls. |
README.md |
Documents the new reusable selector usage patterns. |
| def __init__(self, *args, **kwargs): | ||
| super().__init__() | ||
|
|
||
| # Initialize internal fields (bypass __setitem__ to avoid mask update) | ||
| dict.__setitem__(self, self.__mask, 0) | ||
| dict.__setitem__(self, self.__childOrSibling, []) | ||
| dict.__setitem__(self, self.__childOrSiblingSelector, []) | ||
|
|
||
| # Extract source selector (support both positional and kwarg) | ||
| selector = None | ||
| if args and len(args) == 1: | ||
| selector = args[0] | ||
| if "selector" in kwargs: | ||
| selector = kwargs.pop("selector") | ||
|
|
||
| if selector and isinstance(selector, Selector): | ||
| self._copy_from(selector) | ||
|
|
||
| # Apply remaining kwargs (triggers __setitem__ + mask update) | ||
| for k, v in kwargs.items(): | ||
| self[k] = v |
There was a problem hiding this comment.
Selector.init currently accepts arbitrary positional args but silently ignores unsupported values (e.g., Selector('foo') or Selector({'text': 'Clock'}) results in an empty selector). This makes mistakes like d('Clock') no longer raise and can lead to hard-to-debug behavior. Consider validating inputs: allow at most one positional argument and require it (and/or the selector= kwarg) to be a Selector, and raise TypeError when both positional and selector= are provided or when extra positional args are given.
| # Make selector reusable | ||
| clock_icon = Selector(text='Clock', className='android.widget.TextView') | ||
| # Use selector as positional argument | ||
| d(clock_icon) | ||
| # Use selector as keyword argument | ||
| d(selector=clock_icon) |
There was a problem hiding this comment.
README example creates clock_icon = Selector(...) but the surrounding Quick Start examples only import uiautomator2 as u2 (no Selector symbol in scope). This snippet will raise NameError as written. Please update the example to either reference u2.Selector(...) or add an explicit from uiautomator2 import Selector in the snippet/context.
| def test_exists2(app: u2.Device): | ||
| # New writing style | ||
| addition_label = Selector(text="Addition") | ||
| # Use selector as positional argument | ||
| assert app(addition_label).exists | ||
| # Use selector as keyword argument | ||
| assert app(selector=addition_label).exists(timeout=.1) | ||
|
|
||
| not_exist_label = Selector(text="should-not-exists") | ||
| assert not app(not_exist_label).exists | ||
| assert not app(selector=not_exist_label).exists(timeout=.1) | ||
|
|
||
| # Use selector as parameter for other methods | ||
| assert app(addition_label).right(not_exist_label) is None | ||
|
|
There was a problem hiding this comment.
These new assertions cover the reusable Selector feature, but make cov (used by CI) only runs tests/ and does not execute demo_tests/, so this behavior may remain untested in automated checks. Consider adding a small unit test under tests/ that validates Selector positional/keyword passing (and any expected TypeError cases) without requiring a device.
| return UiObject(self, Selector(*args, **kwargs)) | ||
|
|
||
|
|
There was a problem hiding this comment.
Widening Device.call to accept *args means invalid positional calls that previously raised (e.g., d('Clock')) will now be accepted and forwarded into Selector(*args, **kwargs). This is safe only if Selector strictly validates positional args; otherwise the call can silently create an empty selector. After adding validation in Selector, consider also raising early here (or documenting the accepted positional form) to keep the public API predictable.
| return UiObject(self, Selector(*args, **kwargs)) | |
| """ | |
| Create a UiObject selector. | |
| Use keyword arguments, for example: | |
| d(text="Clock") | |
| Positional arguments are not supported and will raise TypeError | |
| to avoid silently creating an unintended empty selector. | |
| """ | |
| if args: | |
| raise TypeError( | |
| "Positional arguments are not supported in Device.__call__. " | |
| "Use keyword arguments, e.g. d(text='Clock')." | |
| ) | |
| return UiObject(self, Selector(**kwargs)) |
2.Add compatibility test cases for the new selector syntax
|
|
若以POM设计模式编写UI自动化脚本, Positional argument: d(Selector(...)) 可能非常常用,其简略了对参数名的书写。新修改加强了对参数的检查,可以避免传错参数难以排查的问题。 |


