一、项目整体结构
common:用于存放一些公共方法文件;
config:用于存放相关配置文件;
outputs:用于存放log及测试报告文件;
pageobjects:用于存放测试的相关方法封装文件;
testcase:用于存放测试用例文件;
二、common文件夹
common文件中目前主要存放了4个基础方法文件,本别是项目相关路径、被测app相关信息、页面元素基础操作方法、yaml文件读取写入方法文件,以下分别介绍具体内容。
1.all_path.py
此文件主要用于配置相关文件的读取路径,包括:log文件存储路径、测试报告输出路径、图片存储路径等。
import os
base_path = os.path.dirname(os.path.dirname(__file__)) # 获取当前文件所在目录的上两级路径即项目根目录
appPath = os.path.join(base_path, 'app')
appium_server_logPath = os.path.join(base_path, 'Outputs', 'appium_log.log')
configPath = os.path.join(base_path, 'Config')
outputsPath = os.path.join(base_path, 'Outputs')
logPath = os.path.join(base_path, 'Outputs', 'logs')
picturePath = os.path.join(base_path, 'Outputs', 'picture')
reportsPath = os.path.join(base_path, 'Outputs', 'reports')
2.app_info.py
存放cmd命令操作app方法,当前项目中暂未使用待后续研究熟悉后可使用该文件封装的方法进行自动启动、停止appium服务及操作app。
import subprocess
import logging
import os
from Common.all_path import appPath
def exec_cmd(cmd) -> str:
result = os.popen(cmd).read()
return result
def get_app_name(file_dir) -> str:
for root, dirs, files in os.walk(file_dir):
files = [file for file in files if file.endswith(".apk")]
if len(files) == 1:
return files[0]
else:
raise FileNotFoundError("{}目录下没有测试包或者存在多个测试包 ".format(file_dir))
def get_app_package_name() -> str:
cmd = "aapt dump badging {} | findstr package".format(os.path.join(appPath, get_app_name(appPath)))
result = exec_cmd(cmd)
if "package" in result:
package_name = result.strip().split(" ")[1].split('=')[1]
return package_name
else:
raise NameError("未获取到package name")
def get_app_launchable_activity() -> str:
cmd = "aapt dump badging {} | findstr launchable".format(os.path.join(appPath, get_app_name(appPath)))
result = exec_cmd(cmd)
if "launchable" in result:
launchable_activity = result.strip().split(" ")[1].split('=')[1].replace("label", '')
return launchable_activity
else:
raise NameError("未获取到launchable activity")
def get_devices_version(device: str) -> str:
if not isinstance(device, str):
raise Exception("device type is should str..")
result = exec_cmd("adb -s {} shell getprop ro.build.version.release".format(device))
result = result.strip()
if "error" not in result:
return result
else:
raise Exception("获取设备系统版本失败,无法进行正常测试")
def get_all_devices() -> list:
result = exec_cmd('adb devices')
result = result.strip().split(" ")[3].replace("\n", '').replace("\t", ''). \
replace("attached", '').split('device')
result.remove('')
if len(result) == 0:
raise Exception("电脑未连接设备信息,无法进行正常测试")
return result
def get_device_infos():
""" [{'platform_version': '8.1.0', 'server_port': 4723, 'system_port': 8200, 'device': 'HDP9K18629901709'}] """
device_infos = []
devices = get_all_devices()
for i in range(len(devices)):
device_dict = {"platform_version": get_devices_version(devices[i]), "server_port": 4723 + i * 2,
"system_port": 8200 + i * 1, "device": devices[i]}
device_infos.append(device_dict)
if len(device_infos) < 1:
raise Exception("当前电脑未连接设备信息。。。")
return device_infos
def uninstall_app(device_list: list) -> None:
""" 卸载 app 命令:adb -s 127.0.0.1:HDP9K18629901709 uninstall "com.xkw.client """
if not isinstance(device_list, list):
raise Exception("device_list is should list!")
for device_info in device_list:
cmd = 'adb -s {} uninstall "{}"'.format(device_info.get("device").split(':')[-1],
str(get_app_package_name())).replace("'", '')
logging.info("开始卸载设备上应用:{}".format(cmd))
exec_cmd(cmd)
def install_app():
""" 下载app """
subprocess.Popen('chcp 65001', shell=True)
cmd = 'adb install {}'.format(os.path.join(appPath, get_app_name(appPath)))
logging.info("开始下载设备上应用:{}".format(cmd))
subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE)
def check_kill_appium(port):
""" 备选方案 杀掉appium 在端口只有1个的时候作用 """
cmd = 'netstat -o -n -a | findstr {}'.format(port['server_port'])
result = exec_cmd(cmd)
if "LISTENING" in result:
package_name = result.strip().split(" ")[-1]
cmds = 'taskkill /f /pid {}'.format(package_name)
result = exec_cmd(cmds)
if 'PID' in result:
logging.info('释放端口: {} 成功'.format(result))
return result
else:
raise NameError("未成功: 终止 PID 为 {} 的进程".format(result))
else:
raise NameError("appium服务未开启")
def kill_app():
""" 备选方案 杀掉appium 在端口只有1个的时候作用 """
device_dict = get_device_infos()[-1]
cmd = 'netstat -o -n -a | findstr {}'.format(device_dict['server_port'])
result = exec_cmd(cmd)
if "LISTENING" in result:
cmds = 'taskkill /f /t /im node.exe'
result = exec_cmd(cmds)
if 'PID' in result:
return result
else:
raise NameError("未成功: 终止 PID 为 {} 的进程".format(result))
else:
raise NameError("appium服务未开启")
3.base.py
base文件主要存放已经封装好的一些页面元素操作方法,包括:查找元素、点击元素、获取元素文本等...
import time
import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from appium.webdriver.common.touch_action import TouchAction
from Common.all_path import picturePath
import datetime
import os
import logging
class Base:
@classmethod
# 自定义一个元素查找方法
def find_element(cls, driver, feature, timeout=5, poll=1.0):
# feature = By.XPATH,"//*[@text='显示']"
"""
依据用户传入的元素信息特征,然后返回当前用户想要查找元素
:param poll: 可选参数,等待元素可见的轮询间隔时间,默认为 1.0 秒。
:param timeout: 可选参数,等待元素可见的最长时间,默认为 5秒。
:param driver:
:param feature: 字典类型,包含用户希望的查找方式,及该方式对应的值
:return: 返回当前用户查找的元素
"""
try:
by = feature['type']
value = feature['value']
doc = feature['doc']
logging.info("页面开始查找元素:{},定位类型:{},值:{}".format(doc, by, value))
wait = WebDriverWait(driver, timeout, poll)
if by == 'XPATH':
loc = wait.until(lambda x: x.find_element(By.XPATH, value))
elif by == 'ID':
loc = wait.until(lambda x: x.find_element(By.ID, value))
elif by == 'CLASS_N