devhelmkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. devhelmkit/__init__.py +44 -0
  2. devhelmkit/android/__init__.py +7 -0
  3. devhelmkit/android/driver.py +18 -0
  4. devhelmkit/assets/so/arm64-v8a/agent_v1.so +0 -0
  5. devhelmkit/assets/so/arm64-v8a/agent_v2.so +0 -0
  6. devhelmkit/assets/so/x86_64/agent.so +0 -0
  7. devhelmkit/core/__init__.py +3 -0
  8. devhelmkit/core/base_component.py +214 -0
  9. devhelmkit/core/base_driver.py +414 -0
  10. devhelmkit/core/base_window.py +25 -0
  11. devhelmkit/core/selector_spec.py +102 -0
  12. devhelmkit/entry.py +90 -0
  13. devhelmkit/exceptions.py +56 -0
  14. devhelmkit/harmony/__init__.py +3 -0
  15. devhelmkit/harmony/agent/__init__.py +7 -0
  16. devhelmkit/harmony/agent/so_manager.py +300 -0
  17. devhelmkit/harmony/config.py +124 -0
  18. devhelmkit/harmony/device/__init__.py +6 -0
  19. devhelmkit/harmony/device/hdc.py +430 -0
  20. devhelmkit/harmony/driver.py +1416 -0
  21. devhelmkit/harmony/finder/__init__.py +12 -0
  22. devhelmkit/harmony/finder/component_finder.py +336 -0
  23. devhelmkit/harmony/finder/popup_handler.py +81 -0
  24. devhelmkit/harmony/finder/selector_adapter.py +101 -0
  25. devhelmkit/harmony/finder/xpath_query.py +110 -0
  26. devhelmkit/harmony/rpc/__init__.py +12 -0
  27. devhelmkit/harmony/rpc/client.py +126 -0
  28. devhelmkit/harmony/rpc/proxy_v2.py +106 -0
  29. devhelmkit/harmony/rpc/remote_object.py +48 -0
  30. devhelmkit/harmony/uiobject.py +246 -0
  31. devhelmkit/harmony/uiwindow.py +43 -0
  32. devhelmkit/harmony/webview/__init__.py +17 -0
  33. devhelmkit/harmony/webview/chromedriver_manager.py +251 -0
  34. devhelmkit/harmony/webview/devtools_finder.py +131 -0
  35. devhelmkit/harmony/webview/webview_driver.py +326 -0
  36. devhelmkit/model/__init__.py +3 -0
  37. devhelmkit/model/action.py +147 -0
  38. devhelmkit/model/app_state.py +50 -0
  39. devhelmkit/model/constants.py +22 -0
  40. devhelmkit/model/display.py +32 -0
  41. devhelmkit/model/format_string.py +24 -0
  42. devhelmkit/model/input.py +50 -0
  43. devhelmkit/model/json_base.py +42 -0
  44. devhelmkit/model/keys.py +375 -0
  45. devhelmkit/model/match_pattern.py +15 -0
  46. devhelmkit/model/page.py +13 -0
  47. devhelmkit/model/params.py +42 -0
  48. devhelmkit/model/rect.py +58 -0
  49. devhelmkit/model/runnable.py +18 -0
  50. devhelmkit/utils/__init__.py +3 -0
  51. devhelmkit/utils/logger.py +72 -0
  52. devhelmkit/utils/retry.py +46 -0
  53. devhelmkit/utils/timeout.py +64 -0
  54. devhelmkit-0.1.0.dist-info/METADATA +411 -0
  55. devhelmkit-0.1.0.dist-info/RECORD +58 -0
  56. devhelmkit-0.1.0.dist-info/WHEEL +5 -0
  57. devhelmkit-0.1.0.dist-info/licenses/LICENSE +201 -0
  58. devhelmkit-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,126 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """RpcClient:设备端 API 调用与返回值处理。
4
+
5
+ 统一使用 bin 模式(proxy_v2),废弃 hap 模式。
6
+ 负责:
7
+ - 构造 RPC 消息(api/this/args/message_type)
8
+ - 调用 proxy_v2 发送
9
+ - 处理返回值(解析 JSON、错误检测、异常分类)
10
+ - 远程对象生命周期管理
11
+
12
+ 仅支持 HarmonyOS 5.0.0+(API 12+),设备端 uitest 服务统一使用
13
+ api9+ 命名(Driver/Component/On),不再兼容 api8 旧命名。
14
+ """
15
+ import json
16
+ from typing import Any, Optional, TYPE_CHECKING
17
+
18
+ from devhelmkit.exceptions import (
19
+ AgentError,
20
+ BackendObjectDroppedError,
21
+ RpcError,
22
+ )
23
+ from devhelmkit.harmony.rpc.proxy_v2 import rpc as rpc_send
24
+ from devhelmkit.harmony.rpc.remote_object import RemoteObjectManager
25
+ from devhelmkit.utils import logger
26
+
27
+ if TYPE_CHECKING:
28
+ from devhelmkit.harmony.device.hdc import HdcDevice
29
+
30
+
31
+ class RpcClient:
32
+ """RPC 客户端,统一使用 bin 模式。"""
33
+
34
+ def __init__(self, device: 'HdcDevice'):
35
+ self._device = device
36
+ self._remote_objects = RemoteObjectManager()
37
+
38
+ def call(self, api_name: str, this_ref: str, args: list) -> Any:
39
+ """调用设备端 API。
40
+
41
+ Args:
42
+ api_name: API 名称(api9+ 风格,如 "Driver.click"、"On.text")
43
+ this_ref: 对象引用(如 'Driver#0'、'Component#3')
44
+ args: 参数列表(递归处理 FrontEndClass → ref, JsonBase → dict, Enum → value)
45
+
46
+ Returns:
47
+ 设备端返回结果
48
+
49
+ Raises:
50
+ RpcError: RPC 调用失败
51
+ BackendObjectDroppedError: 后端对象引用已失效
52
+ AgentError: 设备端 Agent 异常
53
+ """
54
+ msg = {
55
+ 'api': api_name,
56
+ 'this': this_ref,
57
+ 'args': args,
58
+ 'message_type': 'hypium'
59
+ }
60
+ logger.debug("RPC -> %s this=%s args=%r", api_name, this_ref, args)
61
+ reply = rpc_send(self._device, msg)
62
+ result = self._handle_reply(reply, api_name)
63
+ logger.debug("RPC <- %s result=%r", api_name, result)
64
+ return result
65
+
66
+ def _handle_reply(self, reply: str, api_name: str) -> Any:
67
+ """处理设备端返回值,解析 JSON 并检测错误。"""
68
+ try:
69
+ result = json.loads(reply)
70
+ except json.JSONDecodeError as e:
71
+ logger.error("RPC 返回值解析失败 [%s]: %s", api_name, reply[:200])
72
+ raise RpcError(
73
+ "RPC 返回值解析失败: %s" % e,
74
+ api=api_name, reply=reply
75
+ ) from e
76
+
77
+ error = result.get('error')
78
+ if error:
79
+ logger.warn("RPC 调用失败 [%s]: %s", api_name, error)
80
+ return self._raise_for_error(error, api_name, reply)
81
+
82
+ # 设备端异常时可能返回 {"exception":{"code":...,"message":...}}
83
+ exception = result.get('exception')
84
+ if exception:
85
+ msg = exception.get('message', str(exception))
86
+ logger.warn("RPC 调用异常 [%s]: %s", api_name, msg)
87
+ raise RpcError(
88
+ "RPC 调用异常: %s" % msg,
89
+ api=api_name, reply=reply
90
+ )
91
+
92
+ return result.get('result')
93
+
94
+ @staticmethod
95
+ def _raise_for_error(error: str, api_name: str, reply: str) -> None:
96
+ """根据错误信息抛出对应异常。"""
97
+ error_lower = str(error).lower()
98
+
99
+ # 后端对象引用失效
100
+ if 'object' in error_lower and ('dropped' in error_lower or
101
+ 'released' in error_lower or
102
+ 'invalid' in error_lower):
103
+ raise BackendObjectDroppedError(
104
+ "后端对象引用已失效: %s" % error,
105
+ api=api_name, reply=reply
106
+ )
107
+
108
+ # Agent 进程异常
109
+ if 'agent' in error_lower or 'uitest' in error_lower or \
110
+ 'crash' in error_lower:
111
+ raise AgentError("Agent 异常: %s" % error)
112
+
113
+ raise RpcError(
114
+ "RPC 调用失败 [%s]: %s" % (api_name, error),
115
+ api=api_name, reply=reply
116
+ )
117
+
118
+ @property
119
+ def device(self) -> 'HdcDevice':
120
+ """底层设备通道,供 Captures/Gestures 等非 callHypiumApi 模块复用。"""
121
+ return self._device
122
+
123
+ @property
124
+ def remote_objects(self) -> RemoteObjectManager:
125
+ """远程对象管理器。"""
126
+ return self._remote_objects
@@ -0,0 +1,106 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """bin 模式 RPC 传输,保留设备端协议。
4
+
5
+ proxy_v2 不读取 device 内部代理字段,只调用统一传输入口 device.rpc_call()。
6
+ 设备端协议消息格式:
7
+ {
8
+ "module": "com.ohos.devicetest.hypiumApiHelper",
9
+ "method": "callHypiumApi",
10
+ "params": <内层消息字典>,
11
+ "request_id": "<时间戳>"
12
+ }
13
+ """
14
+ import json
15
+ from datetime import datetime
16
+ from typing import TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING:
19
+ from devhelmkit.harmony.device.hdc import HdcDevice
20
+
21
+
22
+ def _generate_request_id() -> str:
23
+ """生成基于时间戳的请求 ID。"""
24
+ return datetime.now().strftime("%Y%m%d%H%M%S%f")
25
+
26
+
27
+ def _send(device: 'HdcDevice', method: str, params: dict) -> str:
28
+ """构造 bin 模式 RPC 消息并发送至设备端。
29
+
30
+ 统一负责 request_id 生成、full_msg 组装、JSON 序列化与设备调用,
31
+ 对外函数仅构造 params 后委托本函数。
32
+
33
+ Args:
34
+ device: HdcDevice 实例,提供 rpc_call 传输入口
35
+ method: 设备端协议 method 字段(如 callHypiumApi/Captures/Gestures)
36
+ params: 内层 params 消息字典
37
+
38
+ Returns:
39
+ 设备端回显的 JSON 字符串
40
+ """
41
+ full_msg = {
42
+ "module": "com.ohos.devicetest.hypiumApiHelper",
43
+ "method": method,
44
+ "params": params,
45
+ "request_id": _generate_request_id()
46
+ }
47
+ full_msg_str = json.dumps(full_msg, ensure_ascii=False,
48
+ separators=(',', ':'))
49
+ return device.rpc_call(full_msg_str)
50
+
51
+
52
+ def rpc(device: 'HdcDevice', msg: dict) -> str:
53
+ """发送 bin 模式 RPC,返回设备端回显字符串。
54
+
55
+ Args:
56
+ device: HdcDevice 实例,提供 rpc_call 传输入口
57
+ msg: 内层消息字典(含 api/this/args/message_type)
58
+
59
+ Returns:
60
+ 设备端回显的 JSON 字符串
61
+ """
62
+ return _send(device, "callHypiumApi", msg)
63
+
64
+
65
+ def rpc_captures(device: 'HdcDevice', api: str, args: dict) -> str:
66
+ """发送 Captures 模块 RPC(如 captureLayout)。
67
+
68
+ Captures 模块协议与 callHypiumApi 不同:
69
+ - method 为 "Captures"
70
+ - params 中无 this/message_type,args 为对象而非数组
71
+
72
+ Args:
73
+ device: HdcDevice 实例
74
+ api: API 名称(如 "captureLayout")
75
+ args: 参数对象
76
+
77
+ Returns:
78
+ 设备端回显的 JSON 字符串
79
+ """
80
+ params = {
81
+ "api": api,
82
+ "args": args
83
+ }
84
+ return _send(device, "Captures", params)
85
+
86
+
87
+ def rpc_gestures(device: 'HdcDevice', api: str, args: dict) -> str:
88
+ """发送 Gestures 模块 RPC(如 touchDown/touchMove/touchUp)。
89
+
90
+ Gestures 模块协议与 callHypiumApi 不同:
91
+ - method 为 "Gestures"
92
+ - params 中无 this/message_type,args 为对象(如 {x, y})而非数组
93
+
94
+ Args:
95
+ device: HdcDevice 实例
96
+ api: API 名称(如 "touchDown")
97
+ args: 参数对象
98
+
99
+ Returns:
100
+ 设备端回显的 JSON 字符串
101
+ """
102
+ params = {
103
+ "api": api,
104
+ "args": args
105
+ }
106
+ return _send(device, "Gestures", params)
@@ -0,0 +1,48 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """远程对象生命周期管理。
4
+
5
+ 跟踪设备端远程对象引用(如 Driver#0、Component#3、On#seed)的创建与回收。
6
+ 活跃对象使用 WeakSet 跟踪,待回收对象累积超过 batch_size 时触发批量清理。
7
+ """
8
+ from typing import Any, Set
9
+ from weakref import WeakSet
10
+
11
+
12
+ DEFAULT_BATCH_SIZE = 20
13
+
14
+
15
+ class RemoteObjectManager:
16
+ """管理设备端远程对象引用的生命周期。"""
17
+
18
+ def __init__(self) -> None:
19
+ self.total_registered_objects = 0
20
+ self.total_removed_objects = 0
21
+ self.batch_size = DEFAULT_BATCH_SIZE
22
+ # 待回收的对象引用集合
23
+ self.pending_release_refs: Set[str] = set()
24
+ # 活跃对象弱引用集合(不阻止 GC)
25
+ self.active_objects = WeakSet()
26
+ # 标记是否已执行 release_all,禁止后续 add_object
27
+ self._released = False
28
+
29
+ def add_object(self, obj: Any) -> None:
30
+ """注册远程对象到活跃集合。"""
31
+ if self._released:
32
+ return
33
+ remains = len(self.active_objects)
34
+ self.active_objects.add(obj)
35
+ if len(self.active_objects) > remains:
36
+ self.total_registered_objects += 1
37
+
38
+ def remove_object(self, backend_obj_ref: str) -> None:
39
+ """标记远程对象引用为待回收。"""
40
+ self.total_removed_objects += 1
41
+ self.pending_release_refs.add(backend_obj_ref)
42
+
43
+ def release_all(self) -> None:
44
+ """释放所有远程对象。"""
45
+ self._released = True
46
+ for item in self.active_objects:
47
+ if hasattr(item, 'release'):
48
+ item.release()
@@ -0,0 +1,246 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """UiObject:HarmonyOS 平台控件对象。
4
+
5
+ UiObject 只持有 SelectorSpec 与驱动引用,不直接拼底层 RPC 消息。
6
+ 控件查找、选择器转换、远程对象引用和弹窗处理统一交给 HarmonyDriver /
7
+ ComponentFinder。每次操作都重新定位控件,对齐 U2 单对象模型。
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Optional, Tuple, Union, TYPE_CHECKING
12
+
13
+ from devhelmkit.core.base_component import BaseComponent
14
+ from devhelmkit.core.selector_spec import SelectorSpec, build_selector
15
+ from devhelmkit.model.rect import Rect
16
+
17
+ if TYPE_CHECKING:
18
+ from PIL.Image import Image
19
+ from devhelmkit.harmony.driver import HarmonyDriver
20
+
21
+
22
+ class UiObject(BaseComponent):
23
+ """HarmonyOS 平台控件对象。
24
+
25
+ 所有操作通过 HarmonyDriver 门面委托给 ComponentFinder,
26
+ 由 ComponentFinder 负责 By/On 链构造、findComponent 和方法调用。
27
+ """
28
+
29
+ # ============================================================
30
+ # 内部:控件操作门面
31
+ # ============================================================
32
+
33
+ def _call_component(self, method: str,
34
+ args: Optional[list] = None) -> Any:
35
+ """通过驱动门面执行控件操作。"""
36
+ return self._driver.call_component(self._selector, method, args or [])
37
+
38
+ # ============================================================
39
+ # 点击类
40
+ # ============================================================
41
+
42
+ def click(self) -> None:
43
+ self._call_component("click")
44
+
45
+ def long_click(self, duration: float = 0.5) -> None:
46
+ self._call_component("longClick", [duration])
47
+
48
+ def double_click(self) -> None:
49
+ self._call_component("doubleClick")
50
+
51
+ def click_exists(self, timeout: float = 0) -> bool:
52
+ return self._call_component("clickIfExists", [timeout])
53
+
54
+ # ============================================================
55
+ # 文本类
56
+ # ============================================================
57
+
58
+ def set_text(self, text: str) -> None:
59
+ self._call_component("setText", [text])
60
+
61
+ def get_text(self) -> str:
62
+ return self._call_component("getText")
63
+
64
+ def clear_text(self) -> None:
65
+ self._call_component("clearText")
66
+
67
+ def input_text(self, text: str) -> None:
68
+ self.set_text(text)
69
+
70
+ # ============================================================
71
+ # 状态类
72
+ # ============================================================
73
+
74
+ def exists(self) -> bool:
75
+ return self._driver.component_exists(self._selector)
76
+
77
+ def wait(self, timeout: float) -> bool:
78
+ return self._driver.wait_component(self._selector, timeout)
79
+
80
+ def wait_gone(self, timeout: float) -> bool:
81
+ return self._driver.wait_component_gone(self._selector, timeout)
82
+
83
+ # ============================================================
84
+ # 信息类
85
+ # ============================================================
86
+
87
+ def _collect_properties(self) -> dict:
88
+ """收集控件共有属性(文本、类型、布尔状态)。
89
+
90
+ info 与 get_all_properties 共享此基础集合,
91
+ info 在此基础上补充 description 与 bounds。
92
+ """
93
+ return {
94
+ "text": self._call_component("getText"),
95
+ "id": self._call_component("getId"),
96
+ "type": self._call_component("getType"),
97
+ "enabled": self._call_component("isEnabled"),
98
+ "focused": self._call_component("isFocused"),
99
+ "selected": self._call_component("isSelected"),
100
+ "clickable": self._call_component("isClickable"),
101
+ "long_clickable": self._call_component("isLongClickable"),
102
+ "scrollable": self._call_component("isScrollable"),
103
+ "checkable": self._call_component("isCheckable"),
104
+ "checked": self._call_component("isChecked"),
105
+ }
106
+
107
+ @property
108
+ def info(self) -> dict:
109
+ """控件综合信息(文本、类型、状态、边界等)。
110
+
111
+ 设备端无 getInfo API,逐项调用属性方法组装。
112
+ """
113
+ props = self._collect_properties()
114
+ props["description"] = self._call_component("getDescription")
115
+ props["bounds"] = self._call_component("getBounds")
116
+ return props
117
+
118
+ @property
119
+ def bounds(self) -> Rect:
120
+ result = self._call_component("getBounds")
121
+ return _to_rect(result)
122
+
123
+ def center(self) -> Tuple[int, int]:
124
+ return self.bounds.center.as_tuple()
125
+
126
+ def get_attribute(self, name: str) -> Any:
127
+ return self._call_component("getAttribute", [name])
128
+
129
+ def screenshot(self, filename: Optional[str] = None) -> Union['Image', str, None]:
130
+ return self._driver.screenshot(filename=filename, area=self._selector)
131
+
132
+ @property
133
+ def description(self) -> str:
134
+ return self._call_component("getDescription")
135
+
136
+ def get_hint(self) -> str:
137
+ return self._call_component("getHint")
138
+
139
+ def get_all_properties(self) -> dict:
140
+ """获取控件全部布尔属性与文本。"""
141
+ return self._collect_properties()
142
+
143
+ def get_original_text(self) -> str:
144
+ return self._call_component("getOriginalText")
145
+
146
+ # ============================================================
147
+ # 布尔属性
148
+ # ============================================================
149
+
150
+ @property
151
+ def is_long_clickable(self) -> bool:
152
+ return self._call_component("isLongClickable")
153
+
154
+ @property
155
+ def is_checked(self) -> bool:
156
+ return self._call_component("isChecked")
157
+
158
+ @property
159
+ def is_checkable(self) -> bool:
160
+ return self._call_component("isCheckable")
161
+
162
+ @property
163
+ def is_selected(self) -> bool:
164
+ return self._call_component("isSelected")
165
+
166
+ # ============================================================
167
+ # 拖拽类
168
+ # ============================================================
169
+
170
+ def drag_to(self, x: int, y: int) -> None:
171
+ self._call_component("dragTo", [x, y])
172
+
173
+ def drag_to_component(self, other: 'UiObject') -> None:
174
+ self._call_component("dragToComponent", [other._selector])
175
+
176
+ # ============================================================
177
+ # 缩放类
178
+ # ============================================================
179
+
180
+ def pinch_in(self, scale: float = 0.5) -> None:
181
+ self._call_component("pinchIn", [scale])
182
+
183
+ def pinch_out(self, scale: float = 1.5) -> None:
184
+ self._call_component("pinchOut", [scale])
185
+
186
+ # ============================================================
187
+ # 滚动类
188
+ # ============================================================
189
+
190
+ def scroll_search(self, target, vertical: bool = True,
191
+ offset: Optional[int] = None) -> Optional['UiObject']:
192
+ return self._driver.scroll_search(
193
+ self._selector, target, vertical, offset
194
+ )
195
+
196
+ def scroll_to_top(self, speed: int = 600) -> None:
197
+ self._call_component("scrollToTop", [speed])
198
+
199
+ def scroll_to_bottom(self, speed: int = 600) -> None:
200
+ self._call_component("scrollToBottom", [speed])
201
+
202
+ # ============================================================
203
+ # 关系选择器:返回新的 UiObject,持有衍生 SelectorSpec
204
+ # ============================================================
205
+
206
+ def child(self, **kwargs) -> 'UiObject':
207
+ child_spec = build_selector(**kwargs)
208
+ new_spec = SelectorSpec.with_relation(self._selector, 'child', child_spec)
209
+ return UiObject(self._driver, new_spec)
210
+
211
+ def sibling(self, **kwargs) -> 'UiObject':
212
+ sibling_spec = build_selector(**kwargs)
213
+ new_spec = SelectorSpec.with_relation(
214
+ self._selector, 'sibling', sibling_spec
215
+ )
216
+ return UiObject(self._driver, new_spec)
217
+
218
+ def after(self, **kwargs) -> 'UiObject':
219
+ after_spec = build_selector(**kwargs)
220
+ new_spec = SelectorSpec.with_relation(self._selector, 'after', after_spec)
221
+ return UiObject(self._driver, new_spec)
222
+
223
+ def before(self, **kwargs) -> 'UiObject':
224
+ before_spec = build_selector(**kwargs)
225
+ new_spec = SelectorSpec.with_relation(self._selector, 'before', before_spec)
226
+ return UiObject(self._driver, new_spec)
227
+
228
+
229
+ def _to_rect(data: Any) -> Rect:
230
+ """将设备端返回的坐标数据转为 Rect。
231
+
232
+ 设备端 getBounds 可能返回字典或列表,统一转换为 Rect。
233
+ """
234
+ if isinstance(data, dict):
235
+ return Rect(
236
+ left=int(data.get('left', 0)),
237
+ top=int(data.get('top', 0)),
238
+ right=int(data.get('right', 0)),
239
+ bottom=int(data.get('bottom', 0)),
240
+ )
241
+ if isinstance(data, (list, tuple)) and len(data) >= 4:
242
+ return Rect(
243
+ left=int(data[0]), top=int(data[1]),
244
+ right=int(data[2]), bottom=int(data[3]),
245
+ )
246
+ return Rect(left=0, top=0, right=0, bottom=0)
@@ -0,0 +1,43 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """UiWindow:HarmonyOS 平台窗口对象。
4
+
5
+ 窗口操作能力较控件操作少,当前阶段保留基础结构,
6
+ 后续按需补充基于 Driver.findWindow 的实现。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING
11
+
12
+ from devhelmkit.core.base_window import BaseWindow
13
+
14
+ if TYPE_CHECKING:
15
+ from devhelmkit.harmony.driver import HarmonyDriver
16
+ from devhelmkit.harmony.rpc.client import RpcClient
17
+
18
+
19
+ class UiWindow(BaseWindow):
20
+ """HarmonyOS 平台窗口对象。
21
+
22
+ 持有驱动引用,通过 RPC 调用设备端窗口 API。
23
+ """
24
+
25
+ def __init__(self, driver: 'HarmonyDriver',
26
+ rpc: 'RpcClient', window_id: Optional[int] = None):
27
+ self._driver = driver
28
+ self._rpc = rpc
29
+ self._window_id = window_id
30
+
31
+ @property
32
+ def size(self) -> Tuple[int, int]:
33
+ """窗口大小,设备端暂无窗口级尺寸 API,回退为屏幕尺寸。"""
34
+ return self._driver.get_display_size()
35
+
36
+ @property
37
+ def info(self) -> Dict[str, Any]:
38
+ """窗口信息,设备端暂无窗口信息 API,返回空字典占位。"""
39
+ return {}
40
+
41
+ def get_windows(self) -> List['UiWindow']:
42
+ """获取所有窗口,设备端暂无枚举窗口 API,返回空列表占位。"""
43
+ return []
@@ -0,0 +1,17 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """webview 自动化子模块。
4
+
5
+ 提供 webview 页面级自动化能力,基于 selenium webdriver。
6
+
7
+ 使用方式:
8
+ from devhelmkit.harmony.webview import WebViewDriver
9
+
10
+ wv = WebViewDriver(device)
11
+ wv.connect("com.huawei.hmos.browser")
12
+ wv.driver.get("https://www.baidu.com")
13
+ wv.close()
14
+ """
15
+ from devhelmkit.harmony.webview.webview_driver import WebViewDriver
16
+
17
+ __all__ = ["WebViewDriver"]