Skip to content

add reusable Selector support and flexible argument passing#1167

Open
Zeros2619 wants to merge 2 commits into
openatx:masterfrom
Zeros2619:my-contribution
Open

add reusable Selector support and flexible argument passing#1167
Zeros2619 wants to merge 2 commits into
openatx:masterfrom
Zeros2619:my-contribution

Conversation

@Zeros2619

Copy link
Copy Markdown
Contributor
  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

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
@codeskyblue

Copy link
Copy Markdown
Member

Positional argument: d(Selector(...)). 这个要不去掉好了,很容易传错参数。另外给 selector参数加个类型断言 Optional[Selector]

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 Selector and Device.__call__ to accept an existing Selector via positional arg or selector= 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.

Comment thread uiautomator2/_selector.py Outdated
Comment on lines +46 to +66
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

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +495 to +500
# 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)

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +41
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

Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment thread uiautomator2/__init__.py Outdated
Comment on lines 467 to 469
return UiObject(self, Selector(*args, **kwargs))


Copilot AI Feb 28, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
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))

Copilot uses AI. Check for mistakes.
2.Add compatibility test cases for the new selector syntax
@sonarqubecloud

Copy link
Copy Markdown

@Zeros2619

Copy link
Copy Markdown
Contributor Author

若以POM设计模式编写UI自动化脚本, Positional argument: d(Selector(...)) 可能非常常用,其简略了对参数名的书写。新修改加强了对参数的检查,可以避免传错参数难以排查的问题。

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.

3 participants