添加mumu-pytest测试框架
88
mumu-pytest/README.md
Normal 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
@@ -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)
|
||||||
3
mumu-pytest/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pytest>=7.0.0
|
||||||
|
pyautogui>=0.9.54
|
||||||
|
requests>=2.28.0
|
||||||
BIN
mumu-pytest/running.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
mumu-pytest/start.png
Normal file
|
After Width: | Height: | Size: 942 B |
27
mumu-pytest/test_mumu.py
Normal 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
|
After Width: | Height: | Size: 2.7 KiB |
BIN
mumu-pytest/web_address.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
mumu-pytest/web_debug.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
mumu-pytest/web_debug_apk.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
mumu-pytest/web_debug_apk_download.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
mumu-pytest/web_debug_apk_install.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
mumu-pytest/web_debug_apk_open.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
mumu-pytest/web_debug_apk_run.png
Normal file
|
After Width: | Height: | Size: 996 B |
BIN
mumu-pytest/web_goon.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |