Skip to content

Commit 78f77f9

Browse files
committed
refactor: init uitest service
1 parent 0031134 commit 78f77f9

File tree

7 files changed

+119
-65
lines changed

7 files changed

+119
-65
lines changed

README.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,13 @@ d.uninstall_app("com.kuaishou.hmapp")
200200
### 启动App
201201

202202
```python
203+
d.start_app("com.kuaishou.hmapp")
204+
203205
d.start_app("com.kuaishou.hmapp", "EntryAbility")
204206
```
205-
传入的两个参数分别是`package_name`, `page_name`,可以通过hdc命令获取`hdc shell aa dump -l`
207+
`package_name`, `page_name`分别为包名和ability name,可以通过hdc命令获取`hdc shell aa dump -l`
206208

209+
不传`page_name`时,默认会使用main ability作为`page_name`
207210

208211
### 停止App
209212
```python
@@ -258,6 +261,23 @@ d.get_app_info("com.kuaishou.hmapp")
258261
}
259262
```
260263

264+
### 获取App main ability
265+
```python
266+
d.get_app_main_ability("com.kuaishou.hmapp")
267+
```
268+
269+
输出的数据结构是Dict, 内容如下
270+
271+
```
272+
{
273+
"name": "EntryAbility",
274+
"moduleName": "kwai",
275+
"moduleMainAbility": "EntryAbility",
276+
"mainModule": "kwai",
277+
"isLauncherAbility": true,
278+
"score": 2
279+
}
280+
```
261281

262282
## 设备操作
263283
### 获取设备信息

hmdriver2/_client.py

+77-59
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
# -*- coding: utf-8 -*-
2-
import re
32
import socket
43
import json
54
import time
65
import os
7-
import glob
6+
import hashlib
87
import typing
9-
import subprocess
8+
from typing import Optional
109
from datetime import datetime
1110
from functools import cached_property
1211

@@ -143,8 +142,7 @@ def invoke_captures(self, api: str, args: typing.List = []) -> HypiumResponse:
143142

144143
def start(self):
145144
logger.info("Start HmClient connection")
146-
self._init_so_resource()
147-
self._restart_uitest_service()
145+
_UITestService(self.hdc).init()
148146

149147
self._connect_sock()
150148

@@ -163,47 +161,79 @@ def release(self):
163161
logger.error(f"An error occurred: {e}")
164162

165163
def _create_hdriver(self) -> DriverData:
166-
logger.debug("create uitest driver")
164+
logger.debug("Create uitest driver")
167165
resp: HypiumResponse = self.invoke("Driver.create") # {"result":"Driver#0"}
168166
hdriver: DriverData = DriverData(resp.result)
169167
return hdriver
170168

171-
def _init_so_resource(self):
172-
"Initialize the agent.so resource on the device."
173-
174-
file_postfix = ".so"
175-
device_agent_path = "/data/local/tmp/agent.so"
176-
arch_info = self.hdc.shell("file /system/bin/uitest").output.strip()
177-
if "x86_64" in arch_info:
178-
file_postfix = ".x86_64_so"
179-
local_path = ""
180-
local_ver = "0.0.0"
181-
for agent_file in glob.glob(os.path.join(ASSETS_PATH, "uitest_agent*so")):
182-
file_name = os.path.split(agent_file)[1]
183-
if not agent_file.endswith(file_postfix):
184-
continue
185-
matcher = re.search(r'\d{1,3}[.]\d{1,3}[.]\d{1,3}', file_name)
186-
if not matcher:
187-
continue
188-
ver = matcher.group()[0]
189-
if ver.split('.') > local_ver.split('.'):
190-
local_ver, local_path = ver, agent_file
191-
device_ver_info = self.hdc.shell(f"cat {device_agent_path} | grep -a UITEST_AGENT_LIBRARY").output.strip()
192-
matcher = re.search(r'\d{1,3}[.]\d{1,3}[.]\d{1,3}', device_ver_info)
193-
device_ver = matcher.group(0) if matcher else "0.0.0"
194-
logger.debug(f"local agent version {local_ver}, device agent version {device_ver}")
195-
if device_ver.split('.') < local_ver.split('.'):
196-
logger.debug(f"start update agent, path is {local_path}")
197-
self._kill_uitest_service()
198-
for file in AGENT_CLEAR_PATH:
199-
self.hdc.shell(f"rm /data/local/tmp/{file}*")
200-
self.hdc.send_file(local_path, device_agent_path)
201-
self.hdc.shell(f"chmod +x {device_agent_path}")
202-
logger.debug("Update agent finish.")
203-
else:
204-
logger.debug("Device agent is up to date!")
205-
206-
def get_devicetest_proc_pid(self):
169+
170+
class _UITestService:
171+
def __init__(self, hdc: HdcWrapper):
172+
"""Initialize the UITestService class."""
173+
self.hdc = hdc
174+
175+
def init(self):
176+
"""
177+
Initialize the UITest service:
178+
1. Ensure agent.so is set up on the device.
179+
2. Start the UITest daemon.
180+
181+
Note: 'hdc shell aa test' will also start a uitest daemon.
182+
$ hdc shell ps -ef |grep uitest
183+
shell 44306 1 25 11:03:37 ? 00:00:16 uitest start-daemon singleness
184+
shell 44416 1 2 11:03:42 ? 00:00:01 uitest start-daemon com.hmtest.uitest@4x9@1"
185+
"""
186+
187+
logger.debug("Initializing UITest service")
188+
local_path = self._get_local_agent_path()
189+
remote_path = "/data/local/tmp/agent.so"
190+
191+
self._kill_uitest_service() # Stop the service if running
192+
self._setup_device_agent(local_path, remote_path)
193+
self._start_uitest_daemon()
194+
time.sleep(0.5)
195+
196+
def _get_local_agent_path(self) -> str:
197+
"""Return the local path of the agent file."""
198+
target_agent = "uitest_agent_v1.1.0.so"
199+
return os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets", target_agent)
200+
201+
def _get_remote_md5sum(self, file_path: str) -> Optional[str]:
202+
"""Get the MD5 checksum of a remote file."""
203+
command = f"md5sum {file_path}"
204+
output = self.hdc.shell(command).output.strip()
205+
return output.split()[0] if output else None
206+
207+
def _get_local_md5sum(self, file_path: str) -> str:
208+
"""Get the MD5 checksum of a local file."""
209+
hash_md5 = hashlib.md5()
210+
with open(file_path, "rb") as f:
211+
for chunk in iter(lambda: f.read(4096), b""):
212+
hash_md5.update(chunk)
213+
return hash_md5.hexdigest()
214+
215+
def _is_remote_file_exists(self, file_path: str) -> bool:
216+
"""Check if a file exists on the device."""
217+
command = f"[ -f {file_path} ] && echo 'exists' || echo 'not exists'"
218+
result = self.hdc.shell(command).output.strip()
219+
return "exists" in result
220+
221+
def _setup_device_agent(self, local_path: str, remote_path: str):
222+
"""Ensure the remote agent file is correctly set up."""
223+
if self._is_remote_file_exists(remote_path):
224+
local_md5 = self._get_local_md5sum(local_path)
225+
remote_md5 = self._get_remote_md5sum(remote_path)
226+
if local_md5 == remote_md5:
227+
logger.debug("Remote agent file is up-to-date")
228+
self.hdc.shell(f"chmod +x {remote_path}")
229+
return
230+
self.hdc.shell(f"rm {remote_path}")
231+
232+
self.hdc.send_file(local_path, remote_path)
233+
self.hdc.shell(f"chmod +x {remote_path}")
234+
logger.debug("Updated remote agent file")
235+
236+
def _get_uitest_pid(self) -> typing.List[str]:
207237
proc_pids = []
208238
result = self.hdc.shell("ps -ef").output.strip()
209239
lines = result.splitlines()
@@ -215,23 +245,11 @@ def get_devicetest_proc_pid(self):
215245
return proc_pids
216246

217247
def _kill_uitest_service(self):
218-
for pid in self.get_devicetest_proc_pid():
248+
for pid in self._get_uitest_pid():
219249
self.hdc.shell(f"kill -9 {pid}")
220250
logger.debug(f"Killed uitest process with PID {pid}")
221251

222-
def _restart_uitest_service(self):
223-
"""
224-
Restart the UITest daemon.
225-
226-
Note: 'hdc shell aa test' will also start a uitest daemon.
227-
$ hdc shell ps -ef |grep uitest
228-
shell 44306 1 25 11:03:37 ? 00:00:16 uitest start-daemon singleness
229-
shell 44416 1 2 11:03:42 ? 00:00:01 uitest start-daemon com.hmtest.uitest@4x9@1"
230-
"""
231-
try:
232-
self._kill_uitest_service()
233-
self.hdc.shell("uitest start-daemon singleness")
234-
time.sleep(.5)
235-
236-
except subprocess.CalledProcessError as e:
237-
logger.error(f"An error occurred: {e}")
252+
def _start_uitest_daemon(self):
253+
"""Start the UITest daemon."""
254+
self.hdc.shell("uitest start-daemon singleness")
255+
logger.debug("Started UITest daemon")
-1.37 MB
Binary file not shown.

hmdriver2/driver.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,20 @@ def _invoke(self, api: str, args: List = []) -> HypiumResponse:
8383
return self._client.invoke(api, this="Driver#0", args=args)
8484

8585
@delay
86-
def start_app(self, package_name: str, page_name: str = ''):
87-
page_name = page_name or self.get_app_main_ability(package_name).get('name', 'MainAbility')
86+
def start_app(self, package_name: str, page_name: Optional[str] = None):
87+
"""
88+
Start an application on the device.
89+
If the `package_name` is empty, it will retrieve main ability using `get_app_main_ability`.
90+
91+
Args:
92+
package_name (str): The package name of the application.
93+
page_name (Optional[str]): Ability Name within the application to start.
94+
"""
95+
if not page_name:
96+
page_name = self.get_app_main_ability(package_name).get('name', 'MainAbility')
8897
self.hdc.start_app(package_name, page_name)
8998

90-
def force_start_app(self, package_name: str, page_name: str = ""):
99+
def force_start_app(self, package_name: str, page_name: Optional[str] = None):
91100
self.go_home()
92101
self.stop_app(package_name)
93102
self.start_app(package_name, page_name)
@@ -153,9 +162,8 @@ def get_app_abilities(self, package_name: str) -> List[Dict]:
153162
"""
154163
Get the abilities of an application.
155164
156-
157165
Args:
158-
package_name (str): The package name of the application to retrieve information for.
166+
package_name (str): The package name of the application.
159167
160168
Returns:
161169
List[Dict]: A list of dictionaries containing the abilities of the application.

hmdriver2/hdc.py

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def recv_file(self, rpath: str, lpath: str):
114114
return result
115115

116116
def shell(self, cmd: str, error_raise=True) -> CommandResult:
117+
# ensure the command is wrapped in double quotes
117118
if cmd[0] != '\"':
118119
cmd = "\"" + cmd
119120
if cmd[-1] != '\"':

runtest.sh

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
pytest tests --capture=no
44
# pytest tests/test_driver.py::test_toast --capture=no
5+
# pytest tests/test_driver.py::test_toast tests/test_driver.py::test_get_app_main_ability --capture=no

tests/test_driver.py

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ def test_force_start_app(d):
3232
d.force_start_app("com.samples.test.uitest", "EntryAbility")
3333

3434

35+
def test_get_app_main_ability(d):
36+
d.unlock()
37+
ability = d.get_app_main_ability("com.samples.test.uitest")
38+
assert ability.get("name") == "EntryAbility"
39+
40+
3541
def test_clear_app(d):
3642
d.clear_app("com.samples.test.uitest")
3743

0 commit comments

Comments
 (0)