自动化测试: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这种方法。

Read more

唤醒80年代记忆:基于百度地图的一次老式天气预报的WebGIS构建之旅

唤醒80年代记忆:基于百度地图的一次老式天气预报的WebGIS构建之旅

目录 一、省会城市信息构建 1、省会城市空间查询 2、Java后台查询 二、Java省会城市天气查询 1、与百度开放平台集成天气 2、响应对象属性介绍 3、省会天气实况展示 三、WebGIS应用构建 1、背景音乐集成 2、城市标记及天气展示 3、城市轮播 4、成果展示 四、总结 前言         在数字技术飞速发展的今天,我们常常沉浸于各种高科技带来的便捷与震撼之中,却容易忽视那些曾经陪伴我们成长、承载着时代记忆的旧事物。80年代的天气预报,便是这样一份珍贵的文化遗产。它以简洁而质朴的方式,传递着天气信息,也传递着那个时代的气息。那种对自然的敬畏、对信息的渴望,以及一家人共同分享的温馨氛围,都深深烙印在我们的记忆中。然而,随着时间的推移,天气预报的形式已经发生了翻天覆地的变化。高清的画面、精准的数据、个性化的推送……这些现代技术带来的便利固然令人欣喜,但也在一定程度上让我们失去了那份对天气预报本身的纯粹情感。于是,

【通过 Vue 实例劫持突破 Web 编辑器的粘贴限制】

【通过 Vue 实例劫持突破 Web 编辑器的粘贴限制】

逆向实战:通过 Vue 实例劫持突破 Web 编辑器的粘贴限制 * 第一种类型(含分析,可直接跳过至4.1查看方法) * 1. 现象与初探:被禁用的 Ctrl+V * 技术视角的初步审视 * 逆向的逻辑前提 * 2. 逆向分析:寻找逻辑的“命门” * 突破口:利用 I18N 国际化配置追踪 * 核心文件追踪:锁定 `answer-code-editor.js` * 代码逻辑解剖:拦截机制的实现 * 3. 攻克方案:Vue 实例的运行时劫持 * 第一步:获取 Vue 实例的“后门” * 第二步:函数劫持(Monkey Patch) * 第三步:状态机的一致性重构 * 第四步:唤醒底层编辑器 * 4. 最终脚本:

Flutter for OpenHarmony: Flutter 三方库 jaspr 为鸿蒙端开启极速渲染的现代 Web 开发新范式(Dart Web 框架首选)

Flutter for OpenHarmony: Flutter 三方库 jaspr 为鸿蒙端开启极速渲染的现代 Web 开发新范式(Dart Web 框架首选)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 开发时,我们偶尔需要跳出原生的 HAP 容器,寻找更轻量、更适合在移动端 Web 加载的方案。虽然 Flutter Web 极其强大,但其生成的 Canvas/Wasm 产物体积巨大,在鸿蒙系统加载较慢。是否存在一种方案,既能使用 Dart 的声明式开发体验,又能产出纯正、轻量的 HTML/CSS/JS 节点? jaspr 就是这个问题的终极答案。它是一个模仿 Flutter 语法、但专注于渲染原生 Web DOM 的现代框架。通过 Jaspr,鸿蒙开发者可以利用熟悉的 Widget、Component 和生命周期,

Youtu-VL-4B-Instruct源码实战:基于Gradio自定义组件扩展WebUI的图片批处理功能

Youtu-VL-4B-Instruct源码实战:基于Gradio自定义组件扩展WebUI的图片批处理功能 1. 引言:从单张到批量,解放生产力的新思路 如果你用过Youtu-VL-4B-Instruct的WebUI,肯定体验过它的强大——上传一张图片,问几个问题,模型就能给出精准的回答。无论是识别图片里的文字,还是描述复杂的场景,这个40亿参数的多模态模型都表现得相当不错。 但不知道你有没有遇到过这样的场景:手头有几十张产品图片需要批量添加描述,或者有一堆文档截图需要统一提取文字。这时候,一张一张上传、等待、再上传,效率实在太低了。每次操作都要重复“上传-等待-复制结果”的流程,不仅耗时,还容易出错。 这就是我们今天要解决的问题。原生的WebUI界面虽然友好,但在批量处理方面存在明显短板。它就像一家只接受堂食的餐厅,味道很好,但没法做外卖。而我们需要的是能同时处理多份订单的中央厨房。 好消息是,Gradio框架给了我们足够的灵活性。通过深入源码,我们可以自己动手,为这个WebUI增加一个“图片批处理”功能。想象一下,一次性上传几十张图片,设置好统一的提问模板,然后去喝杯咖