Skip to content

Commit f74dbd4

Browse files
committed
feat: support XPath element
1 parent 3b5817d commit f74dbd4

File tree

14 files changed

+254
-55
lines changed

14 files changed

+254
-55
lines changed

README.md

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
- 屏幕录屏
3838
- 手势操作(点击,滑动,输入,复杂手势)
3939
- 支持控件操作
40-
- 控件查找(联合查找,模糊查找,相对查找)
40+
- 控件查找(联合查找,模糊查找,相对查找,xpath查找
4141
- 控件信息获取
4242
- 控件点击,长按,拖拽,缩放
4343
- 文本输入,清除
@@ -122,7 +122,7 @@ UI 控件树可视化工具,查看控件树层级,获取控件详情。
122122
- [息屏](#息屏)
123123
- [屏幕解锁](#屏幕解锁)
124124
- [Key Events](#key-events)
125-
- [执行hdc](#执行hdc)
125+
- [执行 HDC 命令](#执行-hdc-命令)
126126
- [打开URL (schema)](#打开url-schema)
127127
- [文件操作](#文件操作)
128128
- [屏幕截图](#屏幕截图)
@@ -136,17 +136,23 @@ UI 控件树可视化工具,查看控件树层级,获取控件详情。
136136
- [输入](#输入)
137137
- [复杂手势](#复杂手势)
138138
- [控件操作](#控件操作)
139-
- [控件选择器](#控件选择器)
140-
- [控件查找](#控件查找)
141-
- [控件信息](#控件信息)
142-
- [控件数量](#控件数量)
143-
- [控件点击](#控件点击)
144-
- [控件双击](#控件双击)
145-
- [控件长按](#控件长按)
146-
- [控件拖拽](#控件拖拽)
147-
- [控件缩放](#控件缩放)
148-
- [控件输入](#控件输入)
149-
- [文本清除](#文本清除)
139+
- [常规选择器](#常规选择器)
140+
- [控件查找](#控件查找)
141+
- [控件信息](#控件信息)
142+
- [控件数量](#控件数量)
143+
- [控件点击](#控件点击)
144+
- [控件双击](#控件双击)
145+
- [控件长按](#控件长按)
146+
- [控件拖拽](#控件拖拽)
147+
- [控件缩放](#控件缩放)
148+
- [控件输入](#控件输入)
149+
- [文本清除](#文本清除)
150+
- [XPath选择器](#xpath选择器)
151+
- [xpath控件是否存在](#xpath控件是否存在)
152+
- [xpath控件点击](#xpath控件点击)
153+
- [xpath控件双击](#xpath控件双击)
154+
- [xpath控件长按](#xpath控件长按)
155+
- [xpath控件输入](#xpath控件输入)
150156
- [获取控件树](#获取控件树)
151157
- [获取Toast](#获取toast)
152158

@@ -291,7 +297,6 @@ d.set_display_rotation(DisplayRotation.ROTATION_180)
291297
```
292298

293299

294-
295300
### Home
296301
```python
297302
d.go_home()
@@ -324,7 +329,7 @@ d.press_key(KeyCode.POWER)
324329
详细的Key code请参考 [harmony key code](https://github.com/codematrixer/hmdriver2/blob/4d7bceaded947bd63d737de180064679ad4c77b8/hmdriver2/proto.py#L133)
325330

326331

327-
### 执行hdc
332+
### 执行 HDC 命令
328333
```python
329334
data = d.shell("ls -l /data/local/tmp")
330335

@@ -488,7 +493,7 @@ d.click(x, y)
488493

489494
## 控件操作
490495

491-
### 控件选择器
496+
### 常规选择器
492497
控件查找支持这些`by`属性
493498
- `id`
494499
- `key`
@@ -506,6 +511,7 @@ d.click(x, y)
506511
- `isBefore`
507512
- `isAfter`
508513

514+
Notes: 获取控件属性值可以配合 [UI inspector](https://github.com/codematrixer/ui-viewer) 工具查看
509515

510516
**普通定位**
511517
```python
@@ -537,7 +543,7 @@ d(text="showToast", isAfter=True)
537543
d(id="drag", isBefore=True)
538544
```
539545

540-
### 控件查找
546+
#### 控件查找
541547
结合上面讲的控件选择器,就可以进行元素的查找
542548
```python
543549
d(text="tab_recrod").exists()
@@ -550,7 +556,7 @@ d(text="tab_recrod").find_component()
550556
# 当没找到返回None
551557
```
552558

553-
### 控件信息
559+
#### 控件信息
554560

555561
```python
556562
d(text="tab_recrod").info
@@ -603,7 +609,7 @@ d(text="tab_recrod").boundsCenter
603609
```
604610

605611

606-
### 控件数量
612+
#### 控件数量
607613
```python
608614
d(type="Button").count # 输出当前页面`type`为Button的元素数量
609615

@@ -612,7 +618,7 @@ len(d(type="Button"))
612618
```
613619

614620

615-
### 控件点击
621+
#### 控件点击
616622
```python
617623
d(text="tab_recrod").click()
618624
d(type="Button", text="tab_recrod").click()
@@ -624,20 +630,20 @@ d(text="tab_recrod").click_if_exists()
624630
- `click` 如果元素没找到,会报错`ElementNotFoundError`
625631
- `click_if_exists` 即使元素没有找到,也不会报错,相当于跳过
626632

627-
### 控件双击
633+
#### 控件双击
628634
```python
629635
d(text="tab_recrod").double_click()
630636
d(type="Button", text="tab_recrod").double_click()
631637
```
632638

633-
### 控件长按
639+
#### 控件长按
634640
```python
635641
d(text="tab_recrod").long_click()
636642
d(type="Button", text="tab_recrod").long_click()
637643
```
638644

639645

640-
### 控件拖拽
646+
#### 控件拖拽
641647
```python
642648
from hmdriver2.proto import ComponentData
643649

@@ -649,7 +655,7 @@ d(type="ListItem").drag_to(componentB)
649655
```
650656
`drag_to`的参数`component``ComponentData`类型
651657

652-
### 控件缩放
658+
#### 控件缩放
653659
```python
654660
# 将元素按指定的比例进行捏合缩小1倍
655661
d(text="tab_recrod").pinch_in(scale=0.5)
@@ -660,16 +666,54 @@ d(text="tab_recrod").pinch_out(scale=2)
660666
其中`scale`参数为放大和缩小比例
661667

662668

663-
### 控件输入
669+
#### 控件输入
664670
```python
665671
d(text="tab_recrod").input_text("abc")
666672
```
667673

668-
### 文本清除
674+
#### 文本清除
669675
```python
670676
d(text="tab_recrod").clear_text()
671677
```
672678

679+
### XPath选择器
680+
xpath选择器基于标准的xpath规范,也可以使用`//*[@属性="属性值"]`的样式(xpath lite)
681+
```python
682+
d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]')
683+
d.xpath('//*[@text="showDialog"]')
684+
```
685+
686+
控件xpath路径获取可以配合 [UI inspector](https://github.com/codematrixer/ui-viewer) 工具查看
687+
688+
#### xpath控件是否存在
689+
```python
690+
d.xpath('//*[@text="showDialog"]').exists() # 返回True/False
691+
d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]').exists()
692+
```
693+
694+
#### xpath控件点击
695+
```python
696+
d.xpath('//*[@text="showDialog"]').click()
697+
d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]').click_if_exists()
698+
```
699+
以上两个方法有一定的区别
700+
- `click` 如果元素没找到,会报错`XmlElementNotFoundError`
701+
- `click_if_exists` 即使元素没有找到,也不会报错,相当于跳过
702+
703+
#### xpath控件双击
704+
```python
705+
d.xpath('//*[@text="showDialog"]').double_click()
706+
```
707+
708+
#### xpath控件长按
709+
```python
710+
d.xpath('//*[@text="showDialog"]').long_click()
711+
```
712+
713+
#### xpath控件输入
714+
```python
715+
d.xpath('//*[@text="showDialog"]').input_text("adb")
716+
```
673717

674718
## 获取控件树
675719
```python

example.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,9 @@
129129
}
130130
}
131131
"""
132+
133+
# XPath
134+
d.xpath('//*[@text="showDialog"]').click()
135+
d.xpath('//*[@text="showDialog"]').click_if_exists()
136+
d.xpath('//root[1]/Row[1]/Column[1]/Row[1]/Button[3]').click()
137+
d.xpath('//*[@text="showDialog"]').input_text("xxx")

hmdriver2/_gesture.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ class _Gesture:
1414
SAMPLE_TIME_NORMAL = 50
1515
SAMPLE_TIME_MAX = 100
1616

17-
def __init__(self, driver: Driver, sampling_ms=50):
17+
def __init__(self, d: Driver, sampling_ms=50):
1818
"""
1919
Initialize a gesture object.
2020
2121
Args:
22-
driver (Driver): The driver object to interact with.
22+
d (Driver): The driver object to interact with.
2323
sampling_ms (int): Sampling time for gesture operation points in milliseconds. Default is 50.
2424
"""
25-
self.driver = driver
25+
self.d = d
2626
self.steps: List[GestureStep] = []
2727
self.sampling_ms = self._validate_sampling_time(sampling_ms)
2828

@@ -117,7 +117,7 @@ def _create_pointer_matrix(self, total_points: int):
117117
"""
118118
fingers = 1
119119
api = "PointerMatrix.create"
120-
data: HypiumResponse = self.driver._client.invoke(api, this=None, args=[fingers, total_points])
120+
data: HypiumResponse = self.d._client.invoke(api, this=None, args=[fingers, total_points])
121121
return data.result
122122

123123
def _inject_pointer_actions(self, pointer_matrix):
@@ -128,7 +128,7 @@ def _inject_pointer_actions(self, pointer_matrix):
128128
pointer_matrix (PointerMatrix): Pointer matrix to inject.
129129
"""
130130
api = "Driver.injectMultiPointerAction"
131-
self.driver._client.invoke(api, args=[pointer_matrix, 2000])
131+
self.d._client.invoke(api, args=[pointer_matrix, 2000])
132132

133133
def _add_step(self, x: int, y: int, step_type: str, interval: float):
134134
"""
@@ -140,7 +140,7 @@ def _add_step(self, x: int, y: int, step_type: str, interval: float):
140140
step_type (str): Type of step ("start", "move", or "pause").
141141
interval (float): Interval duration in seconds.
142142
"""
143-
point: Point = self.driver._to_abs_pos(x, y)
143+
point: Point = self.d._to_abs_pos(x, y)
144144
step = GestureStep(point.to_tuple(), step_type, interval)
145145
self.steps.append(step)
146146

@@ -179,7 +179,7 @@ def set_point(point_index: int, point: Point, interval: int = None):
179179
if interval is not None:
180180
point.x += 65536 * interval
181181
api = "PointerMatrix.setPoint"
182-
self.driver._client.invoke(api, this=pointer_matrix, args=[0, point_index, point.to_dict()])
182+
self.d._client.invoke(api, this=pointer_matrix, args=[0, point_index, point.to_dict()])
183183

184184
point_index = 0
185185

hmdriver2/_screenrecord.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515

1616

1717
class RecordClient(HmClient):
18-
def __init__(self, serial: str, driver: Driver):
18+
def __init__(self, serial: str, d: Driver):
1919
super().__init__(serial)
20-
self.driver = driver
20+
self.d = d
2121

2222
self.video_path = None
2323
self.jpeg_queue = Queue()
@@ -121,7 +121,7 @@ def stop(self) -> str:
121121
self.release()
122122

123123
# Invalidate the cached property
124-
self.driver._invalidate_cache('screenrecord')
124+
self.d._invalidate_cache('screenrecord')
125125

126126
except Exception as e:
127127
logger.error(f"An error occurred: {e}")

hmdriver2/_uiobject.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from .utils import delay
99
from ._client import HmClient
1010
from .exception import ElementNotFoundError
11-
from .proto import ComponentData, ByData, HypiumResponse, Point, Rect, ElementInfo
11+
from .proto import ComponentData, ByData, HypiumResponse, Point, Bounds, ElementInfo
1212

1313

1414
class ByType(enum.Enum):
@@ -180,9 +180,9 @@ def isScrollable(self) -> bool:
180180
return self.__operate("Component.isScrollable")
181181

182182
@property
183-
def bounds(self) -> Rect:
183+
def bounds(self) -> Bounds:
184184
_raw = self.__operate("Component.getBounds")
185-
return Rect(**_raw)
185+
return Bounds(**_raw)
186186

187187
@property
188188
def boundsCenter(self) -> Point:

0 commit comments

Comments
 (0)