添加mumu-pytest测试框架

This commit is contained in:
2026-03-10 22:31:30 +08:00
parent 7bf441a944
commit 9b3c878159
15 changed files with 329 additions and 0 deletions

88
mumu-pytest/README.md Normal file
View File

@@ -0,0 +1,88 @@
# Mumu 模拟器 pytest 测试框架
## 目录结构
```
mumu-pytest/
├── conftest.py # pytest配置和公共fixture
├── test_mumu.py # 测试用例
├── start.png # 启动按钮图片
├── running.png # 运行中图片
├── web.png # web按钮图片
├── web_address.png # 地址输入框图片
├── web_goon.png # 继续按钮图片
├── web_debug.png # 调试按钮图片
├── web_debug_apk.png # APK按钮图片
├── web_debug_apk_download.png # 下载按钮图片
├── web_debug_apk_open.png # 打开文件按钮图片
├── web_debug_apk_install.png # 安装按钮图片
├── web_debug_apk_run.png # 运行按钮图片
├── requirements.txt # Python依赖
└── README.md # 说明文档
```
## 安装依赖
```bash
pip install -r requirements.txt
```
## 配置
`conftest.py` 中修改以下配置:
```python
MUMU_EXE_PATH = r"C:\Program Files\Netease\MuMu\nx_main\MuMuNxMain.exe"
ADB_PATH = r"C:\Program Files\Netease\MuMu\shell\adb.exe"
WEB_URL = "http://192.168.3.15/"
INSTALLED_PACKAGE_NAME = "com.example.flomo_ai"
```
## 图片说明
将以下图片放入 `mumu-pytest` 目录:
| 文件名 | 说明 |
|--------|------|
| start.png | 启动按钮 |
| running.png | 运行中标识 |
| web.png | Web按钮 |
| web_address.png | 地址输入框 |
| web_goon.png | 继续按钮 |
| web_debug.png | 调试按钮 |
| web_debug_apk.png | APK按钮 |
| web_debug_apk_download.png | 下载按钮 |
| web_debug_apk_open.png | 打开文件按钮 |
| web_debug_apk_install.png | 安装按钮 |
| web_debug_apk_run.png | 运行按钮 |
## 运行测试
```bash
# 运行所有测试
pytest
# 显示详细输出
pytest -v -s
```
## 测试流程
1. 启动模拟器 (start.png)
2. 等待运行中 (running.png)
3. 点击Web按钮 (web.png)
4. 输入URL地址 (web_address.png -> http://192.168.3.15/)
5. 点击继续 (web_goon.png)
6. 点击调试 (web_debug.png)
7. 点击APK (web_debug_apk.png)
8. 点击下载 (web_debug_apk_download.png)
9. 点击打开 (web_debug_apk_open.png)
10. 点击安装 (web_debug_apk_install.png)
11. 点击运行 (web_debug_apk_run.png)
## 注意事项
1. 确保MuMu模拟器已正确安装
2. 需要先截取各个步骤对应的图片
3. pyautogui需要配置合适的置信度和超时时间
4. 确保模拟器路径和ADB路径正确

211
mumu-pytest/conftest.py Normal file
View File

@@ -0,0 +1,211 @@
"""
Mumu模拟器 pytest 测试框架
"""
import pytest
import pyautogui
import time
import os
import subprocess
import requests
from pathlib import Path
# 配置
MUMU_EXE_PATH = r"C:\Program Files\Netease\MuMu\nx_main\MuMuNxMain.exe"
MUMU_PROCESS_NAME = "MuMuNxMain.exe"
ADB_PATH = r"C:\Program Files\Netease\MuMu\shell\adb.exe"
APK_DOWNLOAD_URL = "http://your-server.com/app-release.apk"
LOCAL_APK_PATH = os.path.join(os.path.dirname(__file__), "test_app.apk")
INSTALLED_PACKAGE_NAME = "com.example.flomo_ai"
WEB_URL = "http://192.168.3.15/"
SCRIPT_DIR = os.path.dirname(__file__)
class MumuEmulator:
"""Mumu模拟器控制类"""
def __init__(self):
self.process_name = MUMU_PROCESS_NAME
self.script_dir = SCRIPT_DIR
pyautogui.PAUSE = 1
pyautogui.FAILSAFE = True
def is_running(self) -> bool:
"""检查模拟器是否运行"""
try:
result = subprocess.run(
["tasklist"],
capture_output=True,
text=True
)
return self.process_name in result.stdout
except Exception:
return False
def start(self):
"""启动模拟器"""
if not self.is_running():
subprocess.Popen(MUMU_EXE_PATH)
time.sleep(5)
def find_and_click(self, image_name, confidence=0.8, timeout=30):
"""查找图片并点击"""
image_path = os.path.join(self.script_dir, f"{image_name}.png")
if not os.path.exists(image_path):
print(f"图片不存在: {image_path}")
return False
start_time = time.time()
while time.time() - start_time < timeout:
try:
location = pyautogui.locateOnScreen(image_path, confidence=confidence)
if location:
center = pyautogui.center(location)
pyautogui.click(center)
print(f"点击了: {image_name}")
return True
except Exception as e:
pass
time.sleep(1)
print(f"未找到图片: {image_name}")
return False
def type_text(self, text):
"""输入文本"""
pyautogui.write(text, interval=0.1)
print(f"输入了文本: {text}")
def press_enter(self):
"""按回车"""
pyautogui.press("enter")
print("按下回车")
def wait_for_boot(self, timeout=120):
"""等待模拟器启动完成"""
boot_completed = False
start_time = time.time()
while time.time() - start_time < timeout:
if self.is_running():
time.sleep(5)
boot_completed = True
break
time.sleep(2)
return boot_completed
def check_image_exists(self, image_name, confidence=0.8) -> bool:
"""检查图片是否存在"""
image_path = os.path.join(self.script_dir, f"{image_name}.png")
if not os.path.exists(image_path):
return False
try:
location = pyautogui.locateOnScreen(image_path, confidence=confidence)
return location is not None
except Exception:
return False
def run_sequence(self):
"""按顺序执行任务"""
sequence = [
"start",
"running",
"web",
"web.address",
"web_goon",
"web_debug",
"web_debug_apk",
"web_debug_apk_download",
"web_debug_apk_open",
"web_debug_apk_install",
"web_debug_apk_run"
]
for image_name in sequence:
if image_name == "web.address":
if self.check_image_exists("web.address"):
time.sleep(1)
self.type_text(WEB_URL)
time.sleep(0.5)
self.press_enter()
else:
print("web.address 图片不存在跳过输入URL")
else:
self.find_and_click(image_name, timeout=10)
time.sleep(2)
def close(self):
"""关闭模拟器"""
try:
subprocess.run(["taskkill", "/F", "/IM", self.process_name])
except Exception:
pass
class AdbHelper:
"""ADB命令辅助类"""
def __init__(self):
self.adb_path = ADB_PATH
def exec_cmd(self, *args):
"""执行ADB命令"""
cmd = [self.adb_path] + list(args)
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout, result.stderr
def install_apk(self, apk_path: str) -> bool:
"""安装APK"""
stdout, stderr = self.exec_cmd("install", "-r", apk_path)
return "Success" in stdout
def uninstall_app(self, package_name: str) -> bool:
"""卸载应用"""
stdout, stderr = self.exec_cmd("uninstall", package_name)
return "Success" in stdout
def launch_app(self, package_name: str, activity: str):
"""启动应用"""
self.exec_cmd("shell", "am", "start", "-n", f"{package_name}/{activity}")
def is_app_installed(self, package_name: str) -> bool:
"""检查应用是否已安装"""
stdout, _ = self.exec_cmd("shell", "pm", "list", "packages", package_name)
return package_name in stdout
def pull_file(self, remote_path: str, local_path: str):
"""从模拟器拉取文件"""
self.exec_cmd("pull", remote_path, local_path)
@pytest.fixture(scope="session")
def emulator():
"""模拟器fixture"""
mumu = MumuEmulator()
yield mumu
# 测试结束后清理
if mumu.is_running():
mumu.close()
@pytest.fixture(scope="session")
def adb():
"""ADB fixture"""
return AdbHelper()
@pytest.fixture(scope="session")
def download_apk():
"""下载APK"""
if os.path.exists(LOCAL_APK_PATH):
return LOCAL_APK_PATH
response = requests.get(APK_DOWNLOAD_URL, stream=True)
with open(LOCAL_APK_PATH, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
yield LOCAL_APK_PATH
# 清理
if os.path.exists(LOCAL_APK_PATH):
os.remove(LOCAL_APK_PATH)

View File

@@ -0,0 +1,3 @@
pytest>=7.0.0
pyautogui>=0.9.54
requests>=2.28.0

BIN
mumu-pytest/running.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
mumu-pytest/start.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

27
mumu-pytest/test_mumu.py Normal file
View File

@@ -0,0 +1,27 @@
"""
Mumu模拟器测试
"""
import pytest
def test_start_emulator_sequence(emulator):
"""测试模拟器启动和操作序列"""
assert not emulator.is_running(), "模拟器已启动"
emulator.start()
assert emulator.is_running(), "模拟器启动失败"
def test_run_sequence(emulator):
"""测试按顺序执行任务"""
if not emulator.is_running():
pytest.skip("模拟器未运行")
emulator.run_sequence()
def test_close_emulator(emulator):
"""测试关闭模拟器"""
if emulator.is_running():
emulator.close()
assert not emulator.is_running(), "模拟器关闭失败"

BIN
mumu-pytest/web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
mumu-pytest/web_address.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
mumu-pytest/web_debug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

BIN
mumu-pytest/web_goon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB