Skip to the content.

Flybirds

clip

背景

Flybirds 是一套基于BDD模式的前端UI自动化测试框架,提供了一系列开箱即用的工具和完善的文档。

多端研发对于当今时代的前端开发来说是个绕不过去的话题,为了解决这些问题,行业内推出了很多开发方案,但是跨端 UI 自动化测试的解决方案并不多。

Flybirds从2022年初开源至今,通过与社区内活跃用户的交流和反馈,推出了v0.2 版本的跨端跨框架测试方案,一套脚本多端运行,插件化的架构设计,也方便社区开发者自由加入扩展,一起共建成长。

我们需要一个怎么样的多端测试方案

近几年,每隔一段时间就会有很多新的开发框架出现,带来了更好的开发体验和性能的同时,也给自动化测试创造了很多难题。

我们到底需要一个怎样的多端测试方案呢?从 Flybirds 的视角来说,我们希望多端测试不会成为研发流程中的障碍,特别是多端生态整体呈现欣欣向荣之时,自动化测试方案应和开发方案共同成长。

不论是 Web 、React Native 端,还是Native端,理想的方案应该进行多端适配,保留良好扩展,兼顾更多框架,由社区共同建设,促进整体生态繁荣,因此就有了Flybirds 向社区提供的跨端跨框架测试方案。

插件化架构

插件化架构帮助我们将每一个端的能力拆分开, 插件提供运行时所需的组件、API 和配置,Flybirds 将它们分别注入对应的生命周期。

architecture

文件结构

 																
├─ cli	                        脚手架
├─ core
|   ├─ config_manage.py         配置管理
|   ├─ dsl
|   │    ├─ globalization       国际化处理
|   │    └─ step                Step 列表
|   ├─ global_resource.py       全局配置
|   ├─ launch_cycle             生命周期管理
|   └─ plugin
|        ├─ event               事件管理
|        ├─ plugin_manager.py   插件管理
|        └─ plugins					
|             ├─ android        Andriod 相关处理
|             ├─ ios            iOS 相关处理
|             └─ web            Web 相关处理
├─ report                       报告
├─ template                     模板处理
└─ utils								

特性

使用Flybirds你能够完成大部分的手机端自动化操作,以下是一些帮助入门的特性描述:

环境要求

环境搭建

  1. 使用pip安装flybirds框架,过程中会自动安装所需的 依赖包**

    pip3 install flybirds
    

    在Mac/Linux系统下,需要手动赋予adb可执行权限

    for mac

    cd {your_python_path}/site-packages/airtest/core/android/static/adb/mac
    chmod +x adb
    

    for linux

    cd {your_python_path}/site-packages/airtest/core/android/static/adb/linux
    chmod +x adb
    
  2. 使用脚手架创建项目

    flybirds create 
    

    clicreate

运行前检查

Android、iOS

  1. 请确保配置的测试设备能够正常连接
    • Android: 执行命令 adb devices , 检查设备列表中是否包含测试设备
    • iOS:以tidevice库举例,执行命令 tidevice list,检查设备列表中是否包含测试设备

    Android设备连接 Q&A

    • 请先安装手机对应品牌的官方驱动,确保能使用电脑对手机进行USB调试
    • 确保已经打开了手机中的”开发者选项”,并且打开”开发者选项”内的”允许USB调试”
    • 部分手机需要打开”允许模拟位置”、”允许通过USB安装应用”
    • 关闭电脑上已经安装的手机助手软件,能避免绝大多数问题,请务必在任务管理器中手工结束手机助手进程

    iOS设备连接 Q&A

    • 请先准备一台macOS ,使用xcode部署 iOS-Tagent 成功后,能够在mac或windows机器上连接到iOS手机。请点击链接下载项目代码到本地进行部署。
    • mac 环境通过 Homebrew 安装iproxy brew install libimobiledevice
    • windows 环境安装itunes
  2. 下载安装测试包
    • Android:框架会通过config中配置的packagePath自动下载测试包并安装(请确保手机已经打开”允许安装未知来源“ )。也可手动下载安装:下载地址
    • iOS:
      1. 请手动下载演示APP进行安装:下载地址
      2. 开启wdaproxy: shell tidevice --udid $udid wdaproxy -B $web_driver_angnt_bundle_id -p $port

Web

Web项目,需安装浏览器

# 不带参数的运行将安装默认所有浏览器
playwright install
# 通过提供一个参数来安装特定的浏览器
playwright install webkit
# 查看支持安装的浏览器
playwright install --help

语言环境

检查执行环境中是否已安装了对应的字体,以unbutu系统为例,执行中文用例前,需安装对应的字库

sudo apt-get install ttf-wqy-microhei #文泉驿-微米黑
sudo apt-get install ttf-wqy-zenhei #文泉驿-正黑
sudo apt-get install xfonts-wqy #文泉驿-点阵宋体

运行

运行参数

在终端输入以下内容来查看flybirds运行项目时支持的操作

flybirds run --help

​ 传入用户自定义的参数:

作用:覆盖config配置文件中的相应配置的值,比如:

# 通过参数切换执行平台Android、iOS、Web
flybirds run --define platform=web 

​ 指定失败的场景是否需要重新运行,默认是 ‘True’ ,失败后会自动重跑。

示例:

#失败场景不重跑
flybirds run --no-rerun 

​ 指定case 执行完成后是否生成html测试报告,默认是 ‘True’ ,执行完成后自动生成结果测试报告。

示例:

#不生成测试报告
flybirds run --no-html

示例:

flybirds run --path features -p 5

运行演示

为了帮助使用,项目创建时,会生成中英文的Android、iOS、Web 演示feature,方便用户参考。

features/test/
features/test/android
features/test/android/cn/everything.feature
features/test/android/en/everything.feature
features/test/ios
features/test/ios/cn/everything.feature
features/test/ios/en/everything.feature

以“Android”为例

  1. 执行命令 adb devices , 检查设备列表中是否包含测试设备
  2. 开始运行
    cd {PATH_TO_PROJECT_FOLDER}
    flybirds run -P features/test/android
    

    框架会通过flybirds_config中配置的packagePath自动下载测试包并安装(请确保手机已经打开”允许安装未知来源“ )
    运行结果如下

11 features passed, 0 failed, 0 skipped, 0 untested
23 scenarios passed, 0 failed, 0 skipped, 0 untested
117 steps passed, 0 failed, 0 skipped, 0 undefined, 0 untested
Took 5m21.300s
=====================================================================================
    Multiple Cucumber HTML report generated in:

    /Users/test/my_first_project/report/7eb9162a-9d42-4fde-a5d7-d8d4bca7a8d8/index.html
=====================================================================================

项目细节

项目结构

features目录

基础目录结构如下

feature文件

feature文件包含用户动作,行为特征描述及预期结果的文本,行为特征部分使用Gherkin语言编写。

feature文件,也称为功能文件,有两个目的:文档和自动化测试。

以关键字开头(“功能”、“场景”、“场景大纲”、“当”、“而且”、“那么”……), 文件中的任何位置都允许使用注释行。

功能(Feature) 是被测试功能的一些合理的描述性标题,由场景组成。他们可以选择有一个描述、一个背景和一组标签。

背景(Background) 由一系列类似于场景的步骤组成。它允许您向功能的场景添加一些上下文。在此功能的每个场景之前执行。

场景(Senario) 标题应该是被测试场景的合理描述性标题,由一系列给定条件的步骤组成

场景大纲(Senario Outline) 包含功能的详细描述,可以有一组预期条件和结果来配合您的场景步骤

以下是中文feature例子 featureCN

以下是英文feature例子 feature_en

配置参数

必须配置项

进行移动端测试时,必须配置项:deviceIdpackageName。在IOS设备上测试时,必须额外配置webDriverAgent

flybirds_config.json

​ 请求报文比对时忽略报文节点的顺序或重复的列表差异。默认为:false。仅在requestInterception=true时有效。

​ 请求拦截时,终止路由的域名列表。如:”abortDomainList”: [“google.com”]。仅在requestInterception=true时有效。

schema_url.json

用于对多端页面的schema访问地址进行统一配置

示例:

ele_locator.json

用于对多端元素的定位方式进行统一配置

示例:

Hooks

用户可在以下文件中定义hooks

pscript/dsl/step/hook.py

标签(Tags)

可以使用tag标记不同的场景,方便有选择性的运行。

下面是一个例子 tag

运行有特定tag的场景,多个用逗号隔开

flybirds run -T tag1,tag2

‘-’开头表示运行不包含某tag的场景

flybirds run -T -tag

报告(report)

报告包含汇总Summary和功能(feature)、场景(senario)的执行结果,对于失败的场景(senario),报告中会展示当时的屏幕图像和视频, 下面是一个例子。 report

自定义DSL step

在编写Feature的过程中,可能会遇到提供的公共语句不能满足自身项目的需求,需要自定义语句。比如:需要对接某个内部工具API,此时需要用到自定义语句功能。

自定义语句功能会用到python,如果你不了解这门编程语言,也不必要太担心,因为只会使用到最基础的python语法,这并不会太难。

使用方法

  1. 进入项目目录”psscript/dsl/steps”
  2. 新建.py文件来编写自定义语句
  3. 在feature/steps/steps.py中import该.py文件

示例代码如下 dsl_extend

对于团队内部通用的自定义功能,可以考虑创建一个extend package,flybirds支持动态加载,package命名包含“-flybirds-plugin”即可。

DSL step

DSL step列表

上面例子中的关键字“当”、“而且”和“那么”部分构成了测试用例的操作步骤,这些操作步骤框架中已经通过python实现。

当然在这个架构中, 各端略有不同,主要是各端的平台差异性导致,以下是各端具体支持的 DSL step 列表, 大部分step能够适用于多端

DSL step 语义 适用于
跳转到[] 跳转到指定的url地址 android 、web
等待[]秒 等待一段时间 ALL
页面渲染完成出现元素[] 进入新的页面时检查指定元素是否渲染完成 ALL
点击[] 点击指定属性的元素 ALL
点击文案[] 点击指定文案的元素 ALL
点击屏幕位置[][] 点击屏幕指定位置 ALL
在 [] 中输入[] 在指定选择器中输入字符串 ALL
在[]中清空并输入[] 在指定选择器中清空并输入字符串 Web
向[] 查找[]的元素 向指定方向查找指定属性的元素 ALL
全屏向[] 滑动[] 全屏向指定方向滑动指定距离 ALL
[] 向[] 滑动[] 在指定区域内向指定方向滑动指定距离 ALL
存在[]的文案 检查页面中存在指定的字符串 ALL
不存在[]的文案 检查页面中不存在指定的字符串 ALL
存在[]的元素 检查页面中存在指定属性的元素 ALL
不存在[]的元素 检查页面中不存在指定属性的元素 ALL
元素[]消失 检查页面中指定属性的元素在指定时间内消失 App
文案[]消失 检查页面中指定的字符串在规定时间内从页面消失 App
文案[] 的属性[] 为 [] 检查页面中指定文案的指定属性为指定值 ALL
元素[] 的属性[] 为 [] 检查页面中指定元素的指定属性为指定值 ALL
元素[] 位置[] 秒内未变动 检查页面中指定元素的位置在指定时间内未发生变化 App
[] 的文案为[] 检查页面中指定元素的文案等于指定值 ALL
[] 的文案包含[] 检查页面中指定元素的文案包含指定值 ALL
回到首页 回到首页 ALL
全屏截图 保存当前屏幕图像 ALL
开始录屏 开始录制视频 App
开始录屏超时[] 开始录屏并设置超时时间 App
结束录屏 结束录制视频 ALL
连接设备[] 连接测试设备 App
安装APP[] 安装APP android
删除APP[] 删除APP android
启动APP[] 启动APP App
重启APP 重启APP App
关闭App 关闭App App
登录账号[] 密码[] 使用账号密码进行登录 ALL
退出登录 退出系统登录 ALL
返回上一页 返回上一页面 Android 、 web
在[]中向[]查找[]的元素 在指定 选择器 的元素内 向指定方向滑动查找 指定选择器的元素 ALL
在[]中选择[] 在web页面下拉框元素中选择指定值 web
存在[父选择器]的[子选择器]的元素 存在某个父元素,并且该父元素下存在某个子元素 web
[父选择器]的[子选择器]的文案为[] 存在某个父元素,并且该父元素下某个子元素的文案为指定字符串 web
缓存服务请求[operation[,operation …]] 缓存该服务的最后一次请求报文到本地。
注意: operation 是 url 最后一个\ 和 ? 中间的字符串,即请求名
web
移除请求缓存[operation[,operation …]] 从本地清除该服务的请求缓存报文。
注意: operation 是 url 最后一个\ 和 ? 中间的字符串,即请求名
web
移除所有请求缓存 移除所有请求的缓存报文 web
监听服务[operation[,operation …]]绑定MockCase[mockCaseId[,mockCaseId …]] 监听相关operation的请求并拦截,用mockCaseId的返回报文进行替换
注意:operation 是url 最后一个\ 和 ? 中间的字符串
web
移除服务监听[operation[,operation …]] 移除operation 的请求监听
注意:operation 是url 最后一个\ 和 ? 中间的字符串
web
移除所有服务监听 移除所有请求监听 web
验证服务请求[operation]与[target_data_path]一致 比对相关operation的缓存报文与target_data_path对应的文件内容
注意:operation 是url 最后一个\ 和 ? 中间的字符串
web
验证服务非json请求[operation]与[target_data_path]一致 比对相关operation的非json类型的缓存报文与target_data_path对应的文件内容
注意:operation 是url 最后一个\ 和 ? 中间的字符串
web
验证服务[operation]的请求参数[target_json_path]与[expect_value]一致 检查相关operation的缓存报文体对应参数的值与给定的期望值是否一致
注意:operation 是url 最后一个\ 和 ? 中间的字符串
web
-—- -—- -—-

连接设备[]

连接设备[{param}]

安装APP[]

安装APP[{param}]

删除APP[]

删除APP[{param}]

启动APP[]

启动APP[{param}]

重启APP[]

重启app

回到首页[]

回到首页

登录账号[] 密码[]

登录账号[{param1}]密码[{param2}]

退出登录

退出登录

存在 [] 的文案

存在[字符串{, fuzzyMatch=false, timeout=10}]的文案

开始录屏超时 []

开始录屏超时[time]

开始录屏

开始录屏

结束录屏

结束录屏

等待 [] 秒

等待[time]秒

全屏截图

全屏截图*

跳转到 []

跳转到[页面名称]

移除请求缓存 []

移除请求缓存[operation[,operation …]]

移除所有请求缓存

移除所有请求缓存

监听服务 [] 绑定MockCase []

监听服务[operation[,operation …]]绑定MockCase[mockCaseId[,mockCaseId …]]

移除服务监听 []

移除服务监听[operation[,operation …]]

移除所有服务监听

移除所有服务监听

验证服务请求 [] 与 [] 一致

验证服务请求[operation]与[target_data_path]一致

验证服务非json请求 [] 与 [] 一致

验证服务非json请求[operation]与[target_data_path]一致

验证服务 [] 的请求参数 [] 与 [] 一致

验证服务[operation]的请求参数[target_json_path]与[expect_value]一致

Android端例子

使用cli脚手架创建项目后,可在目录{your project}/features/test/android/中找到以下例子

点击

# language: zh-CN
功能: flybirds功能测试-android click

   场景: 验证点击--点击屏幕位置
     当   启动APP[ctrip.android.view]
     而且 点击屏幕位置[580,1200]
     而且 等待[5]秒
     那么 全屏截图
     那么 关闭App


   场景: 验证点击--点击元素
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=机票]
     而且 点击[text=机票]
     那么 全屏截图


   场景: 验证点击--点击并输入
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=搜索]
     而且 在[text=搜索]中输入[flybirds]
     而且 等待[10]秒
     那么 全屏截图

查找元素

# language: zh-CN
功能: flybirds功能测试-android find element

  场景: 验证元素操作--在页面中查找
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=机票]
     那么 在[android.widget.LinearLayout]中向下查找[text=机票]的元素


   场景: 验证元素操作--在全屏查找
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=机票]
     那么 向下查找[text=租车]的元素

录屏

# language: zh-CN
功能:  flybirds功能测试-android device record

   场景: 验证设备录屏--录屏
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=机票]
     而且 开始录屏
     而且 点击[text=机票]
     而且 等待[10]秒
     那么 结束录屏
     那么 关闭App


   场景:验证设备录屏--录屏超时
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=机票]
     而且  开始录屏超时[50]
     而且  点击文案[机票]
     而且  等待[10]秒
     那么 结束录屏
     那么 关闭App

滑动

# language: zh-CN
功能: flybirds功能测试-android swipe

   场景: 验证滑动--元素滑动
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=租车]
     而且 [text=租车]向上滑动[600]
     而且 等待[5]秒
     那么 元素[text=租车]位置[5]秒内未变动
     那么 存在元素[text=搜索]
     那么 存在[搜索]的文案


   场景: 验证滑动--全屏滑动
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=租车]
     而且 全屏向上滑动[600, readyTime=3, duration=2]
     那么 不存在[text=机票]的元素
     那么 不存在[升级攻略]的文案
     那么 元素[text=机票]消失
     那么 文案[机票]消失

验证元素

# language: zh-CN
功能: flybirds功能测试-android verify element

   场景: 验证元素--元素文案
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=机票]
     那么 [text=机票]的文案为[机票]
     那么 [text=机票]的文案包含[机]


   场景: 验证元素--元素属性
     当  启动APP[ctrip.android.view]
     而且 页面渲染完成出现元素[text=机票]
     那么 元素[text=机票]的属性[text]为机票

iOS端例子

使用cli脚手架创建项目后,可在目录{your project}/features/test/ios/中找到以下例子

点击

# language: zh-CN
功能:  flybirds功能测试-ios click

   场景: 验证点击--点击屏幕位置
     当   启动APP[com.ctrip.inner.wireless]
     而且 点击屏幕位置[580,1200]
     而且 等待[5]秒
     那么 全屏截图
     那么 关闭App


   场景: 验证点击--点击元素
     当  启动APP[com.ctrip.inner.wireless]
     而且 页面渲染完成出现元素[label=机票]
     而且 点击[label=机票]
     那么 全屏截图


   场景: 验证点击--点击并输入
     当  启动APP[com.ctrip.inner.wireless]
     而且 页面渲染完成出现元素[label=搜索]
     而且 在[label=搜索]中输入[flybirds]
     而且 等待[10]秒
     那么 全屏截图

查找元素

# language: zh-CN
功能: flybirds功能测试-ios find element

   场景: 验证元素操作--在页面中查找
     当  启动APP[com.ctrip.inner.wireless]
     而且 页面渲染完成出现元素[label=机票]
     那么 在[ScrollView]中向下查找[label=租车]的元素


   场景: 验证元素操作--在全屏查找
     当  启动APP[com.ctrip.inner.wireless]
     而且 页面渲染完成出现元素[label=机票]
     那么 向下查找[label=租车]的元素

滑动

# language: zh-CN
功能: flybirds功能测试-ios swipe

   场景: 验证滑动--元素滑动
     当  启动APP[com.ctrip.inner.wireless]
     而且 页面渲染完成出现元素[label=租车]
     而且 [label=租车]向上滑动[600]
     而且 等待[5]秒
     那么 元素[label=租车]位置[5]秒内未变动
     那么 存在元素[label=租车]
     那么 存在[租车]的元素


   场景: 验证滑动--全屏滑动
     当  启动APP[com.ctrip.inner.wireless]
     而且 页面渲染完成出现元素[label=租车]
     而且 全屏向上滑动[600, readyTime=3, duration=2]
     那么 不存在[label=机票]的元素
     那么 不存在[升级攻略]的文案
     那么 元素[label=机票]消失
     那么 文案[搜索]消失

验证

  # language: zh-CN
 功能: flybirds功能测试-ios verify element

   场景: 验证元素--元素文案
     当  启动APP[com.ctrip.inner.wireless]
     而且 页面渲染完成出现元素[label=机票]
     那么 [label=机票]的文案为[机票]
     那么 [label=机票]的文案包含[机]


   场景: 验证元素--元素属性
     当  启动APP[com.ctrip.inner.wireless]
     而且 页面渲染完成出现元素[label=机票]
     那么 元素[label=机票]的属性[label]为机票

Web端例子

使用cli脚手架创建项目后,可在目录{your project}/features/test/web/中找到以下例子

点击

# language: zh-CN
功能: web点击

    场景: 点击元素
    假如 跳转页面到[百度]
    而且 点击[#s-top-loginbtn]
    而且 等待[3]秒
    那么 全屏截图


    场景: 点击文案
    假如 跳转页面到[百度]
    而且 点击文案[新闻]
    而且 等待[3]秒
    那么 全屏截图


    场景: 点击屏幕位置
    假如 跳转页面到[百度]
    而且 点击屏幕位置[720,400]
    而且 等待[3]秒
    那么 全屏截图

查找元素

# language: zh-CN
功能: web查找元素

    场景: 在全屏查找
    假如 跳转页面到[百度]
    而且 页面渲染完成出现元素[text=新闻]
    那么 向下查找[text=关于百度]的元素


    场景: 在父元素中查找子元素
    假如 跳转页面到[百度]
    那么 存在[#hotsearch-content-wrapper]的[li.hotsearch-item.odd[data-index="2"]]的元素
    那么 [.s-bottom-layer-content]的[text=帮助中心]文案为[帮助中心]

输入

# language: zh-CN
功能: 输入操作

    场景: 输入
    假如 跳转页面到[百度]
    而且 在[#kw]中输入[flybirds]
    而且 等待[3]秒
    那么 全屏截图


    场景: 清空并输入
    假如 跳转页面到[百度]
    而且 在[#kw]中输入[flybirds]
    而且 等待[3]秒
    那么 在[#kw]中清空并输入[input test]
    那么 全屏截图

页面操作

# language: zh-CN
功能: 页面操作

    场景: 返回上一页
    假如 跳转页面到[列表页]
    而且 页面渲染完成出现元素[text=霸王别姬 - Farewell My Concubine]
    而且 点击[text=霸王别姬 - Farewell My Concubine]
    而且 等待[3]秒
    而且 返回上一页
    而且 等待[2]秒
    那么 结束录屏


    场景: 判断当前页面
    假如 跳转页面到[列表页]
    而且 页面渲染完成出现元素[text=霸王别姬 - Farewell My Concubine]
    而且 点击[text=霸王别姬 - Farewell My Concubine]
    而且 等待[3]秒
    那么 当前页面是[列表详情页]

录屏

# language: zh-CN
功能:  web录屏

    场景:录屏
    假如 跳转页面到[百度]
    而且 页面渲染完成出现元素[text=新闻]
    而且 点击[#s-top-loginbtn]
    而且 等待[10]秒
    那么 结束录屏

下拉框选择

# language: zh-CN
功能: 下拉框

    场景: 下拉框选择
    假如 跳转页面到[携程]
    而且 在[#J_roomCountList]中选择[6间]
    而且 等待[5]秒
    那么 结束录屏

滑动

# language: zh-CN
功能: web滑动

    场景: 滑动
    假如 跳转页面到[列表页]
    而且 等待[2]秒
    而且 [text=霸王别姬 - Farewell My Concubine]向下滑动[600]
    而且 等待[5]秒
    那么 存在元素[text=肖申克的救赎 - The Shawshank Redemption]
    那么 存在[肖申克的救赎 - The Shawshank Redemption]的文案


    场景:全屏滑动
    假如 跳转页面到[列表页]
    而且 等待[2]秒
    而且 全屏向下滑动[600]
    而且 等待[5]秒
    那么 不存在[text=测试]的元素
    那么 不存在[测试]的文案

验证

# language: zh-CN
功能: web元素属性验证

    场景: 验证元素文案
    假如 跳转页面到[百度]
    那么 [text=新闻]的文案为[新闻]
    那么 [text=新闻]的文案包含[新]


    场景: 验证元素属性
    假如 跳转页面到[百度]
    那么 元素[#kw]的属性[name]为wd
    那么 元素[#su]的属性[value]为百度一下

缓存服务请求

# language: zh-CN
功能: 缓存服务请求

    场景:验证缓存服务请求
    假如 缓存服务请求[getRecommendHotelList]
    假如 缓存服务请求[writecookie]
    而且 跳转页面到[携程官网]
    那么 等待[5]秒
    而且 移除请求缓存[getRecommendHotelList]
    而且 移除所有请求缓存


    场景:验证缓存服务请求--同时传入多个参数
    假如 缓存服务请求[getRecommendHotelList,writecookie]
    而且 跳转页面到[携程官网]
    那么 等待[5]秒
    而且 移除请求缓存[getRecommendHotelList,writecookie]

验证服务请求

# language: zh-CN
功能: 验证比较服务请求

    场景: json类型服务请求比对
    假如 缓存服务请求[getRecommendHotelList]
    而且 跳转页面到[携程官网]
    那么 等待[5]秒
    而且 验证服务[getRecommendHotelList]的请求参数[head.syscode]与[PC]一致
    而且 验证服务[getRecommendHotelList]的请求参数[$.cityId]与[2]一致
    而且 验证服务[getRecommendHotelList]的请求参数[cityId]与[2]一致
    而且 验证服务请求[getRecommendHotelList]与[compareData/getRecommendHotelList.json]一致


    场景: 非json类型服务请求比对
    假如 缓存服务请求[writecookie]
    而且 跳转到[携程官网]
    那么 等待[5]秒
    而且 验证服务非json请求[writecookie]与[compareData/writecookie.txt]一致

服务监听Mock

# language: zh-CN
功能: 监听并mock服务请求

    场景: 服务监听与mock
    假如 监听服务[movie]绑定MockCase[4245512]
    当   跳转页面到[列表页]
    那么 等待[10]秒
    而且 移除服务监听[movie]
    而且 移除所有服务监听

多端应用例子

测试用例

功能: 乘机人模块

@p1 @android @web
场景:外露乘机人_选择列表页乘机人
   当   跳转页面到[单程填写页]
   那么 页面渲染完成出现元素[已选乘机人姓名]
   那么 [选择乘机人文案]的文案为[选择乘机人]
   那么 [已选乘机人姓名]的文案为[李易峰]
   那么 [已选乘机人证件类型]的文案为[护照]
   那么 [已选乘机人证件号]的文案为[YHE77]
   那么 存在[乘客类型标签儿童]的元素
   那么 返回上一页

页面对象管理

多端项目中的页面对象管理,是通过json文件进行统一管理,通常存在以下两种情况

  1. 各端相同时,参考以下配置
    // 元素定位配置 ele_locator.json
    {
      "选择乘机人文案": “testid=passger_check”,
      "已选乘机人姓名": “testid=passger_name_checked”,
      "已选乘机人证件类型": “testid=passger_ct_checked”,
      "已选乘机人证件号": “testid=passger_cn_checked”
    }
    
  2. 各端不同时,通过android、ios、web区分 ``` // scheme配置 schema_url.json
    { “单程填写页”: { “android”: “urlschemel://auth_activity”, “ios”: “urlschemel://ios_auth_activity”, “web”: “https://address” } }

// 元素定位配置 ele_locator.json { “乘客类型标签儿童”: { “android”: “textid=passger_type_child”, “ios”: “lableid=passger_type_child”, “web”: “xpath=//html/body/div” } }


## 数据驱动参数化
实际项目中,大部分的自动化测试都是基于数据驱动参数化,因此还需要搭配「 场景大纲+例子」一起使用,这里我们对上面的例子进行改造:

```Gherkin
功能: 乘机人模块

@p1 @android @web
场景大纲:  外露乘机人_选择列表页乘机人
     当   跳转页面到[单程填写页]
     那么 页面渲染完成出现元素[已选乘机人姓名]
     那么 <element>的文案为<title>
     那么 存在[乘客类型标签儿童]的元素
     那么 返回上一页

     例子:
        |   element          |   title      |
        |   选择乘机人文案     |   选择乘机人   |
        |   已选乘机人姓名     |   李易峰      |
        |   已选乘机人证件类型  |   护照       |
        |   已选乘机人证件号    |   YHE77     |

多浏览器并发

依托PlayWright的跨浏览器能力,Flybirds支持所有的现代渲染引擎,包括 Chromium、WebKit 和 Firefox。 image

Flybirds支持多浏览器并发模式,方便高效的进行浏览器兼容性测试

配置参数

// browserType: 配置浏览器内核
  "web_info": {
    "headless": true,
    "browserType": ["firefox","chromium","webkit"],  
    "defaultTimeout": 30
  },

执行命令

# 通过参数指定web执行平台启动的浏览器(多个浏览器时用半角逗号进行分隔)
flybirds run -D browserType=webkit,firefox

自定义框架扩展

Flybirds的插件式设计模式,保留了良好的扩展,未来我们会开放更多。

修改扩展

如果你希望在项目中修改当前扩展,你可以用本地文件替换plugin下面的(app,device,element,app,step,screen,screen_record),并在 “plugin_info.json” 中做相应配置。

比如你希望修改web中screen.py文件:

1. 在本地创建一个py文件命名为 screen.py
2. 在plugin_info.json 的web中添加如下配置:   ```json
"screen": {
"path": "{local_path}/screen.py",
"ns": "screen.plugin.myextend"
}   ```   >  {local_path} 为本地路径,"ns"为包名,注意包名的唯一性(以上包名只是例子不做强制限制)

内部增强包

对于团队内部通用的自定义功能,可以考虑创建一个extend package,Flybirds支持动态加载,package命名包含“-flybirds-plugin”即可。 携程机票内部,针对DevOps的各类工具,增强包中都进行了对接,安装后就可以使用。

其他语种支持

flybirds可以支持40几种语言,在以下文件中增加公共方法的语言配置即可。

flybirds/core/dsl/globalization/i18n.py

示例代码如下 lang

持续集成

cli提供的命令行执行模式,可以非常方便加入各种持续集成工具.

以Jenkins为例:

# Inside the jenkins shell command
cd {PATH_TO_PROJECT_FOLDER}
# Run
flybirds run -P ./features/test/everything.feature
cp -R reports $WORKSPACE

发版计划

我们将按照 SemVer 版本控制规范进行发版。逐步新增功能和代码优化,非常欢迎您加入到我们的共建计划中,在 GitHub 上提出您的宝贵建议,以及在使用时遇到的一切问题,我们也会对此每周进行一次小版本的迭代。您也可以在这里给我们精神支持,点上一颗 Star。

参考链接