系列文章:
基于Appium实现深度UI遍历工具
基于Appium实现深度UI遍历工具(二)
基于Appium实现深度UI遍历工具(三)
基于Appium实现深度UI遍历工具(四)代码实现篇(上)
我们上次分享了代码实战篇上,这次分享下一些简单的封装
首先看下对于webdriver封装
import os.path
import time
from datetime import timedelta, datetime
from appium import webdriver
from common.disapp import make_dis
from common.log import LOG
from selenium.webdriver.support.wait import WebDriverWait
from appium.webdriver.common.touch_action import TouchAction
from common.pictools import opear
class deriver_encapsulation(object):
def __init__(self, port, Testplatform, platform_version, dev, apkname, activity):
self.port = port
self.Testplatform = Testplatform
self.platform_version = platform_version
self.dev = dev
self.apkname = apkname
self.activity = activity
self.driver = self.init()
def init(self):
dis_app = make_dis(self.Testplatform, self.platform_version, self.dev, self.apkname, self.activity)
LOG.info(dis_app)
deriver = webdriver.Remote('http://localhost:{}/wd/hub'.format(str(self.port)), dis_app)
time.sleep(10)
return deriver
def find_ele(self, method, path, timeout=1):
try:
if method == 'id':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_id(path))
elif method == 'xpath':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_xpath(path))
elif method == 'css':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_css_selector(path))
elif method == 'and':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_android_uiautomator
('new Uiselector().%s' % path))
elif method == 'class':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_class_name
(path))
elif method == 'name':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_name
(path))
elif method == 'acces':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_accessibility_id
(path))
elif method == 'text':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_link_text
(path))
elif method == 'partial':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_partial_link_text
(path))
elif method == 'tag':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_element_by_tag_name
(path))
else:
raise NameError('no element,please send tag,xpath,text,id,css,id,tag')
return se
except:
return None
def find_elemens(self, method, path, timeout=1):
try:
if method == 'id':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_id(path))
elif method == 'xpath':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_xpath(path))
elif method == 'css':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_css_selector(path))
elif method == 'and':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_android_uiautomator
('new Uiselector().%s' % path))
elif method == 'class':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_eelements_by_class_name
(path))
elif method == 'name':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_name
(path))
elif method == 'acces':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_accessibility_id
(path))
elif method == 'text':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_link_text
(path))
elif method == 'partial':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_partial_link_text
(path))
elif method == 'tag':
se = WebDriverWait(self.driver, timeout, 0.5).until(lambda x: x.find_elements_by_tag_name
(path))
else:
raise NameError('no element,please send tag,xpath,text,id,css,id,tag')
return se
except:
return None
def install(self, path): # 安装app
self.driver.install_app(path)
def uninstall(self,apkname): # 卸载app
self.driver.remove_app(apkname)
def instal_ios(self, bundleId): # ios
self.driver.remove_app(bundleId)
def close(self): # 关闭app
self.driver.close_app()
def reset(self): # 重置app
self.driver.reset()
def hide_keybord(self): # 隐藏键盘
self.driver.hide_keyboard()
def send_keyevent(self, event): # 只有安卓有
self.driver.keyevent(keycode=event)
def sned_press_keycode(self, keycode): # 安卓有
self.driver.press_keycode(keycode=keycode)
def long_press_keycode(self, keycode): # 长按发送
self.driver.long_press_keycode(keycode)
def current_activity(self):
'''
获取当前的activity
'''
activity = self.driver.current_activity
return activity
def wait_activity(self, activity, times, interval=1):
self.driver.wait_activity(activity, timeout=times, interval=interval)
def run_back(self, second):
self.driver.background_app(seconds=second)
def is_app_installed(self, baoming): # ios需要buildid
self.driver.is_app_installed(baoming)
def launch_app(self): # 启动app
self.driver.launch_app()
def start_acti(self, app_package, app_activity):
self.driver.start_activity(app_package, app_activity)
def ios_lock(self, locktime):
self.driver.lock(locktime)
def yaoshouji(self):
self.driver.shake()
def open_tongzhi(self): # 安卓api 18以上
self.driver.open_notifications()
def renturn_network(self): # 返回网络
network_type = self.driver.network_connection
return network_type
def set_network_type(self, type):
from appium.webdriver.connectiontype import ConnectionType
if type == 'wifi' or type == 'WIFI' or type == 'w' or type == 'WIFI_ONLY':
self.driver.set_network_connection(ConnectionType.WIFI_ONLY)
elif type == 'data' or type == 'DATA' or type == 'd' or type == 'DATA_ONLY':
self.driver.set_network_connection(ConnectionType.DATA_ONLY)
elif type == 'ALL' or type == 'all' or type == 'a' or type == 'ALL_NETWORK_ON':
self.driver.set_network_connection(ConnectionType.ALL_NETWORK_ON)
elif type == 'NO' or type == 'no' or type == 'n' or type == 'NO_CONNECTION':
self.driver.set_network_connection(ConnectionType.NO_CONNECTION)
elif type == 'AIRPLANE_MODE' or type == 'air' or type == 'ar' or type == 'fly':
self.driver.set_network_connection(ConnectionType.AIRPLANE_MODE)
else:
raise NameError('plase wifi ,data,all,no,fly')
def open_location(self):
self.driver.toggle_location_services()
def set_location(self, weidu, jingdu, haiba):
self.driver.set_location(weidu, jingdu, haiba)
def get_size(self):
size = self.driver.get_window_size()
return size
def sendkeys(self, element, text):
element.click()
element.clear()
element.send_keys(text)
def screen(self, filename):
self.driver.get_screenshot_as_file(filename)
def closedriver(self):
self.driver.close()
def killdriver(self):
self.driver.quit()
def get_wiow_size(self): # 获取窗口大小
return self.driver.get_window_size()
def swap(self, s_x, s_y, e_x, e_y): # 从一点到另一点
self.driver.flick(s_x, s_y, e_x, e_y)
def swip_end(self, s_x, s_y, e_x, e_y, duration=100):
self.driver.swipe(s_x, s_y, e_x, e_y, duration=duration)
def toche(self, x, y, duration=500):
self.driver.tap([(x, y)], duration=duration)
def scroll(self, x, y): # 滚动元素
self.driver.scroll(x, y)
def drag_and_drop(self, e1, e2): # 移动元素
self.driver.drag_and_drop(e1, e2)
def contexts_is(self): # 可用
self.driver.contexts()
def push(self, data, path):
self.driver.push_file(data, path)
def pull(self, path):
self.driver.pull_file(path)
def take_screen(self, basepath, bound=None):
yesterday = (datetime.now() + timedelta()).strftime("%Y_%m_%d_%H_%S_%M")
paths = os.path.join(basepath, yesterday + ".png")
self.driver.get_screenshot_as_file(paths)
if bound != None:
opear(paths, bound)
def swpape(self, startx, starty, endx, endy):
LOG.info("scroll from : startX " + str(startx) + ", startY " + str(starty) + ", to endX " + str(
endx) + ",endY " + str(endy))
self.driver.swipe(startx, starty, endx, endy, 1000)
def socrae(self):
'''
滑动
'''
wid_hight = self.get_wiow_size()
whidt = wid_hight['width']
height = wid_hight['height']
startx = whidt * 0.5
starty = height * 0.5
endx = startx / 2
endy = 50
self.swpape(startx, starty, endx, endy)
def page_soucre(self):
'''
获取当前页面的布局
'''
return self.driver.page_source
def click(self, x, y):
'''
固定坐标的点击
'''
t = TouchAction(self.driver)
try:
t.tap(x=x, y=y).wait(10).perform()
except Exception as e:
LOG.error("点击固定坐标异常,原因:{}".format(str(e)))
def longcick(self, x, y):
'''
在某个地方长按
'''
t = TouchAction(self.driver)
t.long_press(x=x, y=y).wait(10)
def doubletap(self, x, y):
t = TouchAction(self.driver)
t.tap(x=x, y=y).wait(10).tap(x=x, y=y).perform()
def draginde(self, x, y, enx, eny, andriod):
if y < 200:
y = 200
t = TouchAction(self.driver)
if andriod is False:
enx -= x
eny -= y
if andriod:
t.long_press(x=x, y=y).move_to(x=x, y=y).release().perform()
else:
t.long_press(x=x, y=y).wait(10).move_to(x=x, y=y).release().perform()
def pinch(self, x, y, enx, eny, isup, height, andriod):
'''
设置app的放大还是缩小
'''
lineCenterX = (x + enx) / 2
lineCenterY = (y + eny) / 2
if x > enx:
x, enx = enx, x
y, eny = eny, y
if y < 200:
y = 200
if eny < 200:
eny = 200
if y > height - 200:
y = height - 200
if eny > height - 200:
eny = height - 200
toochx = TouchAction(self.driver)
touchy = TouchAction(self.driver)
if andriod:
if isup:
toochx.press(x=lineCenterX, y=lineCenterY).move_to(x=x, y=y).release()
touchy.press(x=lineCenterX, y=lineCenterY).move_to(x=enx, y=eny).release()
else:
toochx.press(x=x, y=y).move_to(x=lineCenterX, y=lineCenterY).release()
touchy.press(x=enx, y=eny).move_to(x=lineCenterX, y=lineCenterY).release()
else:
if isup:
toochx.press(x=lineCenterX, y=lineCenterY).wait(10).move_to(100, 0).release()
touchy.press(x=lineCenterX, y=lineCenterY).wait(10).move_to(-100, 0).release()
else:
toochx.press(x=x, y=y).wait(10).move_to(x=lineCenterX - x, y=0).release()
touchy.press(x=enx, y=eny).wait(10).move_to(x=lineCenterX - enx, y=0).release()
主要封装了一些常用的方法,方便后续的调用,增加了隐形等待,让元素定位更加不受外在的影响。
这里面有一个图像处理的方法,代码如下
import os
import cv2
from PIL import Image
def opear(image_path, bound):
'''
操作图片,给图片画坐标,实现点击的位置
'''
image = cv2.imread(image_path)
first_point = (str(bound)[1:-1].split("][")[0])
last_point = (str(bound)[1:-1].split("][")[1])
c1, c2 = (int(first_point.split(",")[0]), int(first_point.split(",")[1])), \
(int(last_point.split(",")[0]), int(last_point.split(",")[1]))
try:
cv2.rectangle(image, c1, c2, (0, 255, 0), 2)
cv2.imwrite(image_path, image)
except:
pass
def imagetovideo(filepath, videofile):
'''
图片转视频
'''
allfile = os.listdir(filepath)
if len(allfile)>0:
allfile.sort(key=lambda fn: os.path.getmtime(filepath + '//' + fn))
fps = 5 #帧率
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 用于mp4格式的生成
image = Image.open(os.path.join(filepath, allfile[0]))
videowriter = cv2.VideoWriter(videofile, fourcc, fps, image.size)#设置
for itemfile in allfile:
path = os.path.join(filepath, str(itemfile))
if os.path.isfile(path):
frame = cv2.imread(path)
videowriter.write(frame)
videowriter.release()#释放
cv2.destroyAllWindows()#销毁整个封装呢,一个是绘制操作的区域,一个是把手机的图片进行视频化处理。
还有一个就是组成字典用于启动webdriver的
def make_dis(platformName, platformVersion, deviceName,package,activity):
dis_app = {}
dis_app['platformName'] = platformName
dis_app['platformVersion'] = platformVersion
dis_app['deviceName'] = deviceName
dis_app['appPackage'] = package
dis_app['appActivity'] = activity
dis_app['androidDeviceReadyTimeout'] = TestandroidDeviceReadyTimeout
dis_app['unicodeKeyboard'] = TestunicodeKeyboard
dis_app['resetKeyboard'] = TestresetKeyboard
dis_app['noReset'] = True
return dis_app
也是很简单的,这里的配置如下
TestandroidDeviceReadyTimeout='30'
TestunicodeKeyboard=True
TestresetKeyboard=True很简单的配置。这里配置相关的,可以在我之前的appium系列中有分享。
需要根据app获取app的一些信息
from androguard.core.bytecodes.apk import APK
#获取包名
def get_apkname(apk):
a = APK(apk, False, "r")
return a.get_package()
#获取启动的activity
def get_apk_lanchactivity(apk):
a = APK(apk, False, "r")
return a.get_main_activity()处理文件的。
1.找最新的文件
2.复制文件
import os
import shutil
def new_file(testdir):
#列出目录下所有的文件
file_list = os.listdir(testdir)
#对文件修改时间进行升序排列
file_list.sort(key=lambda fn:os.path.getmtime(testdir+'//'+fn))
return file_list[-10:-1]
def copy_file(time,path):
new_path=os.path.join(path,time)
if os.path.exists(new_path) is False:
os.makedirs(new_path)
all_file=new_file(testdir=path)
for itemfile in all_file:
paths=os.path.join(path,itemfile)
try:
shutil.copyfile(paths,os.path.join(new_path,itemfile))
except:
passadb相关的封装,封装常用方法。
import platform, os
import random
import re
from androguard.core.bytecodes.apk import APK
nativeKey = "Native Heap"
dalKey = "Dalvik Heap"
totalKey = "TOTAL"
fpskey = "FPS"
net = "net"
def checkPackeExit(uid, processname: str, isAndroid: bool = False):
if processname is None or processname == "":
return False
processname = processname.lower()
grep = isgrep()
if isAndroid:
cmd = 'adb -s {}'.format(uid) + " shell ps | " + grep + " " + processname
pid_line = os.popen(cmd).readlines()
if len(pid_line.__str__().lower().split("\n")) > 2:
return False
else:
pid_line = [str(i).split("\n")[0] for i in pid_line]
for item in pid_line:
if processname in str(item).split(" "):
return True
else:
return False
def isgrep():
'''
判断系统
'''
if platform.system().lower().__contains__("wind"):
grep = 'findstr'
else:
grep = 'grep'
return grep
def perform_home(dev):
'''
实现home键操作
'''
cmd = 'adb -s {} shell input keyevent 3'.format(dev)
os.system(cmd)
def perform_back(dev):
'''
实现返回键
'''
cmd = 'adb -s {} shell input keyevent 4'.format(dev)
os.system(cmd)
def perform_audio(dev):
'''
调整音量
'''
autocmd='adb -s {} shell input keyevent '.format(dev)
allaudio=[autocmd+str(i) for i in range(24,26)]
weight=[1,1]
result = random.choices(population=allaudio, weights=weight)[0]
os.system(result)
def retonescreen(dev):
'''旋转屏幕'''
randstr = 'adb -s {} shell content insert --uri content://settings/system --bind name:s:user_rotation --bind value:i:'.format(
dev)
addlist = [randstr + str(i) for i in range(4)]
choice = [1, 1, 1, 1]
result = random.choices(population=addlist, weights=choice)[0]
os.system(result)
def getMobileInfo(serial):
'''
获取设备信息
'''
all = {
"HUAWEI": "ro.build.version.emui",
"vivo": "ro.vivo.os.build.display.id",
"Xiaomi": "ro.build.version.incremental",
"OPPO": "ro.build.version.opporom",
}
model = os.popen('adb -s %s shell getprop ro.product.model' % serial).read().replace('\n', '')
brand = os.popen('adb -s %s shell getprop ro.product.brand' % serial).read().replace('\n', '')
version = os.popen('adb -s %s shell getprop ro.tools.version.release' % serial).read().replace('\n', '')
kernel = os.popen('adb -s %s shell cat /proc/version' % serial).read()
serialno = os.popen('adb -s %s shell getprop ro.serialno' % serial).read().replace('\n', '')
sdk = os.popen('adb -s %s shell getprop ro.tools.version.sdk' % serial).read().replace('\n', '')
try:
newKernel = re.findall("Linux version (.*?) \(", kernel)[0] + '#' + re.findall("#(.*)", kernel)[0]
except:
newKernel = ''
try:
rom = os.popen('adb -s %s shell getprop %s' % (serial, all[brand])).read().replace('\n', '')
except:
rom = os.popen('adb -s %s shell getprop ro.tools.display.id' % serial).read().replace('\n', '')
if (str(brand).lower() == 'xiaomi'):
rom_verison = os.popen('adb -s %s shell "getprop | grep ro.build.version.incremental"' % serial).read().strip()
rom_verison = (re.match('\[ro.build.version.incremental\]: \[(.*)\]', rom_verison).group(1))
else:
rom_verison = 'unknown'
return model, version, newKernel, serialno, brand, sdk, rom, rom_verison
def getversion(dev):
'''
获取系统版本
'''
devverions = os.popen('adb -s {} shell getprop ro.build.version.release'.format(dev)).readlines()
platform_version = (devverions[0].split('\n')[0])
return platform_version
def getactivity(filepath, apkname):
all_list_activity = []
apk = APK(filepath)
elements = apk.find_tags_from_xml('AndroidManifest.xml', 'activity')
for item in elements:
for key, value in item.attrib.items():
if str(key).endswith("name") and str(value).startswith(apkname):
all_list_activity.append(value)
return all_list_activity
对于测试报告有一个简单的封装
def title(titles):
title = '''<!DOCTYPE html>
<html>
<head>
<title>%s</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 -->
<!-- 注意:如果通过 file:// 引入 Respond.js 文件,则该文件无法起效果 -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
<style type="text/css">
.hidden-detail,.hidden-tr{
display:none;
}
</style>
</head>
<body>
''' % (titles)
connent = '''<div class='col-md-8 col-md-offset-4' style='margin-left: 10%;margin-top: 2%;'>
<h1 style="text-align: center;">{}测试的结果</h1>'''.format(
titles)
title += connent
return title以上是对于一些常用的模块的封装,包括webdriver的封装。其实都是一些很简单的,封装的目的减少代码,另一个就是让层级更加明显。
这里面就是一些简单的封装,没有太多的逻辑。
下一张分享代码最后的核心的地方的编写。
所有代码地址:
https://github.com/liwanlei/appium_uicrawler发现问题,解决问题。遇到问题,慢慢解决问题即可。