自动化测试:web+app端在工作中的具体实现
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
自动化测试:web端修改客户端配置,客户端查看配置是否生效
一、测试环境
1、代码编辑器使用Pycharm,编程语言选择Python,web自动化测试工具选择python第三方库playwright,客户端测试工具选择appium
2、项目架构:web端+app端
3、测试用例管理方法:Pytest
4、python及第三方库版本:python:3.11 playwright:1.57.0 pytest:8.3.3 selenium:4.33 appium-python-client:5.1.1
5、appium server:v2.18.0
二、使用步骤
1.Pycharm中文件脚本目录
1、新建python软件包:tests用来存放测试文件
用pytest整合测试用例所以测试文件以test_开头,比如test_patientscreen.py
2、setting软件包:用来存放测试中用到的配置信息,比如web端登录的用户名密码、要上传的文件、延时时间
都放在setting下面方便自定义配置后期维护,而且python在导入这些setting时很方便能够自动查找。
3、pages软件包:使用PO模式进行web端测试时会用到。PO模式我的理解是将在本次测试中经常用到的界面操作根据当前页面来封装。
比如:一个数据查询页面。
有输入关键词、点击搜索、翻页、拉动进度条…
这些操作都是这个页面下的,就可以把这个界面封装成一个类,这些操作都是这个类下的操作函数
因为对web端操作不可避免要经常在各个界面间来回切换。
当下一次切换到这个数据查询界面时就可以直接调用类中的操作函数。比较方便。
4、log文件夹
放日志的,当有多个测试用例时用日志记录操作步骤和捕获错误是个好办法。
工具是python的logging库
2.关键的对象
1、Browser
scope规划了生成器的生命范围
@pytest.fixture(scope="session")defbrowser()->Browser: playwright = sync_playwright().start() browser = playwright.chromium.launch(headless=False,slow_mo=30000)yield browser browser.close() playwright.stop()2、page
@pytest.fixture(scope="class")defpage(browser:Browser)-> Page: context = browser.new_context() page = context.new_page()yield page page.close() context.close()这里装饰器说明了page生成器的生命范围是class,所以在第一次调用此生成器的测试类中page是可以延续的,即第一个测试用例可能需要先进行登录操作,但是后续的测试函数直接从上一个测试结束时界面状态开始就可以
3、控制app的driver
scope=“class”,appium控制设备在第一次调用driver的class中是连续的,执行完第一个测试用例后在执行第二个之前不会重启app。
@pytest.fixture(scope='class')defcreate_driver(): desired_cap ={"platformName":"Android","platformVersion":"6",# 建议移除,除非必要,Appium会自动检测"deviceName":"设备名称",# 可改为通用名称"udid":"udid",# 关键:使用udid指定你的设备"appPackage":"app名","appActivity":"初始化界面app活动名","automationName":"UiAutomator2","noReset":True,"newCommandTimeout":900,"autoGrantPermissions":True,"unicodeKeyboard":True,"resetKeyboard":True} options = AppiumOptions() options.load_capabilities(desired_cap) driver =Nonetry: driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", options=options) driver.implicitly_wait(30)print("驱动初始化成功,设备已连接")yield driver # 将driver提供给外部使用finally:if driver: driver.quit()print("驱动已关闭")4、日志生成器
调用logging模块后声明日志工具
@pytest.fixture(scope="session")deflogger(): logger = logging.getLogger("日志")# 设置日志记录器级别 logger.setLevel("DEBUG")# 日志文件夹位置 log_dir ="./log"# 判断日志文件夹是否存在,不存在生成一个ifnot os.path.exists(log_dir): os.makedirs(log_dir)# 日志记录位置 log_file = os.path.join(log_dir,'log.txt')# log.txt文件名要存在不然写不进去# 添加日志处理器 log_handler = logging.handlers.RotatingFileHandler(log_file, mode='w', encoding='utf-8')# 日志固定输出格式 log_format ="%(asctime)s-%(levelname)s-%(process)d-%(message)s"# 创建格式化器 fmt = logging.Formatter(fmt=log_format) log_handler.setFormatter(fmt)# 将日志处理器添加到日志记录器中 logger.addHandler(log_handler)yield logger print("销毁日志记录器")有了这个日志工具后,在测试用例文件中要打印什么直接logger.info(“”)或者logger.error(“”)
3、PO模式具体使用
在pages软件包中添加py文件login.py
from playwright.sync_api import Page classPatientScreen:def__init__(self,page:Page): self.page = page defnavigate(self): self.page.get_by_text() self.page.get_by_role()...类PatientScreen是封装的一个页面,__init__方法中声明此类的属性page
def__init____(self,page:Page):#page:Page是表明page参数是Page类型,Page从playwright.sync_api中导入了关于这个页面下的操作如何写,推荐使用playwright自带的codegen录制工具,能自动生成操作相应的代码,很方便(大拇指)
playwright codegen "http://..."在测试文件py中如何调用呢?
classTestPatientScreen:deftest_templatecolor(self,page,create_driver,logger): logger.info("case1:测试一览表背景颜色切换") login_page = WebLogin(page) patientscreen_page = PatientScreen(page)``` 声明测试类后,在测试函数中可以直接调用在common中声明的生成器page
然后声明一个PO模式中类的对象作为我们要操作的对象
比如:
login_page是登录界面的可以执行类中动作的对象
4、总结一下本次写appium定位元素时遇到的新的方法
1、遇到一个在web端上传图片后验证app端是否正确上传了的测试点
问了一下ds,方法还是不错的,还是要感谢python丰富的第三方库
先获取设备上的图片:
logo = create_driver.find_element(By.XPATH,'/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.FrameLayout[1]/android.widget.LinearLayout/android.widget.ImageView') screenshot_bytes = logo.screenshot_as_png withopen(device_logo_path,"wb")as f:#device_logo_path是图片保存路径 f.write(screenshot_bytes)然后再把原图保存在这个工程的一个位置,建议保存在setting下面,因为是测试文件,这样比较规范,比如路径是web_logo_path
比较图片是否是同一张的方法:计算汉明距离
from PIL import Image import imagehash defhash_compare(img1_path,img2_path,threshold=16): hash1 = imagehash.phash(Image.open(img1_path)) hash2 = imagehash.phash(Image.open(img2_path))#计算汉明距离 hamming_distance = hash1-hash2 is_similar = hamming_distance <= threshold if is_similar:print("同一张图片")else:print("不是同一张")threshold是比较的严格程度,越小越严格,最大64
这个我是根据社会获取到的图片和原图进行比较,因为从设备上获取的照片比较模糊,先试了threshold=5被判断为不通过。
然后两张完全不同的图片选择64,竟然会被判断为一样。
取16可以分辨不同的图片,所以最终选了16。
2、web端开启手动滑动功能,远程工具连接设备,鼠标确实能拖动小窗口上下滑动
但是通过appium定位不到这个可滑动的元素
于是采用坐标滑动的方法
defswipe_up(driver,start_x_percent=0.2,start_y_percent=0.8,end_y_percent=0.2): width = driver.get_window_size()['width'] height = driver.get_window_size()['height'] actions = ActionChains(driver) actions.w3c_actions = ActionBuilder(driver,mouse=PointerInput(interaction.POINTER_TOUCH,"TOUCH")) actions.w3c_actions.pointer_action.move_to_location(start_x_percent*width,start_y_percent*height) actions.w3c_actions.pointer_action.pointer_down() actions.w3c_actions.pointer_action.move_to_location(start_x_percent*width,end_y_percent*height) actions.w3c_actions.pointer_action.pointer_up() actions.perform()ps:觉得之前的swipe方法简单又快捷,但是现在好像只支持ActionChains这种方法。