Windows-appium 环境配置
1. 安装node.js
- node.js
-
cmd
下输入命令node -v
可以查看Node.js
的版本。 - 安装好
Node.js
后对其做一些基本设置,均在cmd
下输入命令即可
2. 设置缓存
npm config set cache "D:\Program Files\nodejs
3. 设置全局模块存放路径
npm config set prefix "D:\Program Files\nodejs
4. 设置淘宝npm
镜像
- 通过设置
npm
镜像之后,Node.js
的一些库均可以在淘宝npm
镜像下下载。也就是说以后的npm
命令,我们可以用cnpm
命令代替,访问该镜像网站。在部分电脑下,可能还要配置cnpm
环境,但也是非常简单。例如我的Node.js
安装在D盘,即可在环境变量中添加D:\Program Files\nodejs\node-global
npm config set registry "https://registry.npm.taobao.org"
npm install -g cnpm --registry=https://registry.npm.taobao.org
5. 安装Android SDK
配置Android sdk
环境
6. 安装Appium
运行cmd
npm --registry http://registry.cnpmjs.org install -g appium
- 不指定国内镜像直接安装:
npm install -g appium
如果报错用以下方式安装: - 输入命令:
npm install -g cnpm --registry=https://registry.npm.taobao.org
- 再输入命令:
cnpm install -g appium
- 检验是否成功安装Dos窗口操作命令:
appium
7. 安装Appium-doctor
-
appium-doctor
可以检测appium
整体依赖环境配置情况。在cmd
下输入以下命令就可以安装 cnpm install appium-doctor -g
- 安装完
appium-doctor
环境之后,可以通过appium-doctor
看到如下提示说明整体环境配置成功
8. 配置系统cmd
环境变量
- 将路径
C:\Windows\System32
添加至系统变量path中
项目目录结构说明
.
├─Common ## 公共文件
│ CheckPort.py ## 检查Appium端口
│ StartServer.py ## 启动Appium Server
│ AppDriver.py ## 启用Driver Server
│
└─Config ## 配置文件
│ cap.yaml ## 启动Appium Server参数配置
│ root_config.py ## 系统常量配置
│
└─testcase ## 测试用例
│
└─conftest.py ## pytest fixture 文件
│
└─main.py ## 运行用例文件
CheckPort.py
socket
socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
import socket
import os
def check_port(host, port):
"""检测指定的端口是否被占用"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建socket对象
try:
# 连接到address处的套接字。一般,address的格式为元组(hostname,port)
s.connect((host, port))
s.shutdown(2)
except OSError:
print('port %s is available! ' % port)
return True
else:
print('port %s already be in use !' % port)
return False
def release_port(port):
"""释放指定的端口"""
cmd_find = 'netstat -aon | findstr {}'.format(port) # 查找对应端口的pid
# 返回命令执行后的结果
result = os.popen(cmd_find).read()
if str(port) and 'LISTENING' in result:
# 获取端口对应的pid进程
i = result.index('LISTENING')
start = i + len('LISTENING') + 7
end = result.index('\n')
pid = result[start:end]
cmd_kill = 'taskkill -f -pid %s' % pid # 关闭被占用端口的pid
os.popen(cmd_kill)
print(f'释放进程端口:{port}')
else:
print('port %s is available !' % port)
if __name__ == '__main__':
host = '127.0.0.1'
port = 4723
release_port(4723)
release_port(4725)
if not check_port(host, port):
print("端口被占用")
release_port(port)
StartServer.py
subprocess Popen类
用于在一个新的进程中执行一个子程序;即允许你去创建一个新的进程让其执行另外的程序,并与它进行通信,获取标准的输入、标准输出、标准错误以及返回码等。
主要参数说明:
- args:用于指定进程的可执行文件及其参数。如果是一个序列类型参数,则序列的第一个元素通常都必须是一个可执行文件的路径。当然也可以使用executeable参数来指定可执行文件的路径。
- stdin,stdout,stderr:分别表示程序的标准输入、标准输出、标准错误。有效的值可以是PIPE,存在的文件描述符,存在的文件对象或None,如果为None需从父进程继承过来,stdout可以是PIPE,表示对子进程创建一个管道,stderr可以是STDOUT,表示标准错误数据应该从应用程序中捕获并作为标准输出流stdout的文件句柄。
- shell:如果这个参数被设置为True,程序将通过shell来执行。
- env:它描述的是子进程的环境变量。如果为None,子进程的环境变量将从父进程继承而来。
import subprocess
import time
from config.root_config import LOG_DIR
def appium_start(host, port, log_name):
"""
启动appium server
:param host:
:param port:
:param log_name:
:return:
"""
# 指定bp端口号
bootstrap_port = str(port + 1)
# cmd命令
cmd = 'start /b appium -a ' + host + ' -p ' + str(port) + ' -bp ' + str(bootstrap_port)
# 去掉 “/b”,即可以打开cmd弹窗运行
# cmd = 'start appium -a ' + host+' -p '+str(port) +' -bp '+ str(bootstrap_port)
print("%s at %s " % (cmd, time.ctime()))
# cmd 需要执行的shell命令
subprocess.Popen(cmd, shell=True, stdout=open(LOG_DIR + '/appium_log/' + f'{log_name}.log', 'w',encoding='utf8'),
stderr=subprocess.STDOUT)
if __name__ == '__main__':
host = '127.0.0.1'
port = 4723
conftest.py 配置文件
Hook 方法之 pytest_addoption :
pytest_addoption 可以让用户注册一个自定义的命令行参数,方便用户将数据传递给 pytest;
这个 Hook 方法一般和 内置 fixture pytestconfig 配合使用,pytest_addoption 注册命令行参数,pytestconfig 通过配置对象读取参数的值;
parser.addoption() 参数说明:
- name:自定义命令行参数的名字,可以是:"foo", "-foo" 或 "--foo";
- action:在命令行中遇到此参数时要采取的基本操作类型;
- default:如果参数不在命令行中,则生成的默认值。
- help:对参数作用的简要说明;
import time
import pytest
from Common.app_driver import BaseDriver
from Common.check_port import release_port
base_driver = None
# 注册自定义参数cmdopt到配置对象
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store", default="device_info", help=None)
# 从配置对象获取cmdopt的值
@pytest.fixture(scope="session")
def cmd_opt(request):
return request.config.getoption("--cmdopt")
# 全局启动appium driver
@pytest.fixture(scope="session")
def common_driver(cmd_opt):
cmd_opt = eval(cmd_opt)
global base_driver
base_driver = BaseDriver(cmd_opt)
time.sleep(1)
driver = base_driver.get_base_driver()
driver.implicitly_wait(10)
yield driver
driver.quit() # 退出driver
release_port(cmd_opt["server_port"]) # 释放端口
AppDriver.py
import yaml
from appium import webdriver
from Common.check_port import check_port, release_port
from Common.start_server import appium_start
from config.root_config import CONFIG_PATH
class BaseDriver(object):
def __init__(self, device_info):
self.device_info = device_info
self.host = self.device_info["server_host"]
self.port = int(self.device_info["server_port"])
if not check_port(self.host, self.port):
release_port(self.port)
appium_start(self.host, self.port, self.device_info['title'])
with open(CONFIG_PATH, 'r') as f:
self.data = yaml.load(f, Loader=yaml.FullLoader)
def get_base_driver(self):
"""获取driver"""
caps = self.data[self.device_info['title']]
# 启用webdriver服务
driver = webdriver.Remote(
command_executor='http://' + self.host + ':' + str(self.port) + '/wd/hub',
desired_capabilities=caps # 负责启动服务端时的参数设置
)
return driver
if __name__ == '__main__':
device_info = {"title": "Emulator_two", "server_host": "127.0.0.1", "server_port": "4725", }
base = BaseDriver(device_info)
main.py
import os
import threading
import time
from multiprocessing import Pool
import pytest
from config.root_config import ROOT_DIR
"""多设备启动文件"""
# appium 端口
device_infos = [
{"title": "Emulator_one", "server_host": "127.0.0.1", "server_port": "4723", },
{"title": "Emulator_two", "server_host": "127.0.0.1", "server_port": "4725", }
]
detail_report_path = ROOT_DIR + "\\report\\html"
xml_report_path = ROOT_DIR + "\\report\\xml"
def main(device_info):
pytest.main(["--cmdopt={}".format(device_info),
"--alluredir", xml_report_path, "-vs"])
os.system("allure generate %s -o %s --clean" % (xml_report_path, detail_report_path))
if __name__ == '__main__':
# 创建进程池
with Pool(len(device_infos)) as pool:
# device_infos:要处理的数据列表,main:处理device_infos列表中数据的函数
pool.map(main, device_infos) # 并行
pool.close() # 关闭进程池,不再接受新的进程
pool.join() # 主进程阻塞等待子进程的退出