# 基于行为驱动的自动化测试

# 背景

有多个平台的小程序渠道,测试还是传统的手工测试,一次改动测试回归成本很高,效率相对较低。
同时本身业务迭代还是很频繁,传统的自动化测试方案,维护UI脚本本身成本也很高,对测试人员本身的要求也不一样。

# 技术选型

由当下情况得出的核心诉求:
1. UI 元素不通过脚本获取,降低需要频繁变更的可能
2. 即适合测试人员也适合研发人员,可以支持和当前系统结合
3. 多端的支持,web,android,ios
条件1,3 就抛弃了现在大部分依赖xpath的自动化框架

工具 优点 缺点
Appium 编写测试脚本时间较短,可预装,支持多种语言以及可跨平台进行,由谷歌社区支持和维护。 不能处理flash和web组件,由于不支持iOS设备,当自动化测试同时覆盖Android和iOS的情况时,测试会被中断,Appium server桌面应用程序的发布常常不稳定,在旧设备上变得很慢。
Robotium 自动化app,Android自动跟随,简单易学的教程,且不需要访问源代码。 仅支持Android4.1及以上,不支持脚本记录,不支持web视图,不能跨多个app。
UIAutomation Android自动化测试工具类,通过脚本开发,由于运行绑定到GUI组件,相比Appium,它的测试执行更快更强大。 对测试人员来说编写代码能力要求较高,需要对Android相关知识有一定的了解。
Instrumentation 可以模拟按键按下,抬起,屏幕点击,滚动事件等,第三方支付集成了基于云计算的测试管理。 仅支持Android

条件2就抛弃现有大部分rpa工具,目前已有的rpa应用都偏向低代码的形式,更偏向业务人员

# 核心思路

airtest(处理移动端) + rpa-python(处理桌面端)

  • 实现一个dsl解析器(dsl参考simplerpa (opens new window)
  • 用rpa-python重写airtest-selenium插件(存在兼容性问题,编辑器无法执行)
  • 用chatgpt 将 Cucumber 测试用例转成 yaml配置文件
  • 用chatgpt 将 Cucumber 测试用例转成 airtest 代码,通过airtest编辑器做UI元素调整

Cucumber是一个测试框架,可以用来建立软件开发人员和业务经理之间沟通的渠道。测试脚本基于行为驱动开发(BDD)风格词语编写,如“假如”,“当”、“那么”,或在英文中的Given, When, Then,这样任何非专业人员都能理解。然后将测试用例放入覆盖一个或多个测试场景的剧本(gherkin)文件中。 Cucumber将测试解释成指定的编程语言,然后执行。典型的用法是使用Selenium驱动浏览器中进行测试。

# 屏幕自动化任务

  1. 检查当前桌面上是否显示了需要的页面(比如查看特定位置的图像,或者比对OCR识别出的文字)
  2. 如果确实是,就收集一些文字或图像的信息(这一步未必会有,要看具体任务类型,有些自动化只要把页面流程走通就可以)
  3. 查找页面上特定的控件(比如某个按钮),对它进行操作(如点击)
  • 软件元素识别
    • 获取鼠标所在屏幕坐标
    • 获取鼠标位置的窗体控件
    • 获取控件类型
from pywinauto import Desktop

# 获取当前鼠标位置
mouse_pos = Desktop().mouse.position()

# 获取鼠标位置的窗体控件
control = Desktop().from_point(*mouse_pos)

# 获取控件类型
control_type = control.friendly_class_name()

print('Control type:', control_type)

在 Mac 上,获取鼠标位置的窗体控件和控件类型是一个复杂的任务,因为它需要使用到底层的 Accessibility API。Python 并没有直接提供这样的功能,但是你可以使用一些第三方库来实现。

其中一个可能的选择是 pyobjc 库,它提供了 Python 到 Objective-C 的桥接,可以让你在 Python 中使用 Cocoa 和其他 Mac OS X 的原生 API。

以下是一个使用 pyobjc 来获取鼠标位置的窗体控件和控件类型的基本示例:

from Quartz import CGWindowListCopyWindowInfo, kCGWindowListOptionOnScreenOnly, kCGNullWindowID
from AppKit import NSWorkspace
from PyObjCTools import Conversion
import time

def get_active_window():
    # 获取当前活动的窗口
    active_app = NSWorkspace.sharedWorkspace().activeApplication()
    for window in CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID):
        # 转换 Objective-C 的 NSDictionary 到 Python 的字典
        window = Conversion.pythonCollectionFromPropertyList(window)
        if window['kCGWindowOwnerName'] == active_app['NSApplicationName']:
            return window

while True:
    window = get_active_window()
    if window:
        print('Window name:', window.get('kCGWindowName', 'Unknown'))
        print('Window owner:', window['kCGWindowOwnerName'])
    time.sleep(1)

这个脚本会每秒打印一次当前活动窗口的名称和所有者。然而,这只是获取窗口信息的一部分,获取鼠标位置的窗体控件和控件类型可能需要更深入的使用 Accessibility API,这可能需要更多的 Objective-C 或 Swift 的知识和经验。 请注意,使用 Accessibility API 可能需要用户授予你的应用特定的权限。在 "System Preferences" -> "Security & Privacy" -> "Privacy" -> "Accessibility" 中添加你的应用或脚本,以允许它控制你的电脑。

  • 浏览器元素识别
document.addEventListener('mousemove', function (event) {
  // 获取鼠标所在的屏幕坐标
  var x = event.clientX;
  var y = event.clientY;
  console.log('Mouse coordinates:', x, y);

  // 获取鼠标位置的元素
  var element = document.elementFromPoint(x, y);

  // 获取元素类型
  var elementType = element.tagName;
  console.log('Element type:', elementType);
});
  • 图像识别定位(opencv)
    • 屏幕截取
    • 匹配模版
    • 获取元素位置
  • 图像识别的准确性和稳定性相对较低,且受到环境因素的影响较大,例如屏幕分辨率、颜色设置等
import cv2
import numpy as np
from PIL import ImageGrab

# 截取屏幕图像
screen = np.array(ImageGrab.grab())

# 读取模板
template = cv2.imread('template.png', 0)

# 转换颜色空间
screen_gray = cv2.cvtColor(screen, cv2.COLOR_BGR2GRAY)

# 匹配模板
res = cv2.matchTemplate(screen_gray, template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

# 如果匹配度大于 80%
if max_val > 0.8:
    print('Element found at', max_loc)
  1. 跳转到下一个页面,回到步骤1,反复循环,直到最终页面出现