主题测试自动化:Fastlane篇

行文的主要目的是展示如何用fastlane工具,做多主题在多设备上的测试任务,所以不会对fastlane做全面系统的介绍。但如果在自动化任务中用到fastlane相关技术点,重要的细节、坑或者最佳实践,会在相应的步骤特别的说明。

文章大纲

  1. Fastlane概述,包括fastlane的前世今生
  2. 如何使用Fastlane实现主题测试自动化,步骤分解
  3. 定制输出结果,编写Fastlane的action
  4. Fastlane使用的注意事项

##1. Fastlane概述
Fastlane 作者Felix Krause,于2014年创造了fastlane项目,一年后的2015年被fabric收购,在此之前的2014年,fabric被Crashlytics收购,而Twitter则在2013年收购了Crashlytics。自2015起,作者一直在Twitter开发fastlane,直到2017年Google收购了fabric,现在在Google全职开发fastlane。
从履历上看,fastlane的背后有强大的公司支持和实践,近年来发展势头很好,更新迭代很快。(在前段时间iTunes更改了后台登录的验证方式之后,fastlane团队第一时间获取信息,发布了公告,并在一周内fix)。

fastlane内置了很多常用的actions,如snapshot、deliver、gym等,可以满足大部分场景下的需求,详细用法可在官网查看
事实上,我们可以自己编写actions,那么fastlane内置的actions和我们自定的actions有没有区别呢?

fastlane内置的action的特殊之处

  1. fastlane内置的action,可以直接在命令行里直接运行,如fastlane snapshot,而自定义的action,如后文提到的resort_screenshot,如需在命令行运行,需要使用语法fastlane run resort_screenshot
  2. 内置action有相应的配置文件,如snapshot相应的Snapfile,定义了action需要的参数。(事实上fastlane本身也有一个配置文件Fastfile)
  3. 假设,你在Fastfile(fastlane的配置文件)里创建了一个和内置action重名的lane如snapshot,则fastlane snapshot命令读取到的是内置action,而不是作为lane的snapshot

和actions相关了两个命令,如下:

fastlane actions # list all available fastlane actions
fastlane action [action_name] # more information for a specific action

Fastlane本质上是一系列符合Fastlane规范工具的执行环境。这些工具处于fastlane的管理之下,可以使用fastlane的全局变量、credentials_manager、上游action的输出等数据。fastlane对编写action时,经常使用的组件或util,做了简单的封装,使每个action的行为表现保存一致。如在控制台输出错误提示信息时,可以使用以下语法

 puts "Must name a themeServerId".red 
 // 或者
 UI.error("Must name a themeServerId")

每个lane执行完毕,在控制台都会有相应的统计数据,样式完全一致。fastlane是ruby的一个gem——为了定制自己的actions,你需要了解ruby的基本使用。

如果需要了解详细的关于fastlane的信息,请访问官网docs,Getting started with fastlane for iOS

2. 主题测试自动化步骤

2.1 安装fastlane

确保安装了ruby,版本在2.00以上。假设按照fastlane官网的步骤,你已经成功安装fastlane。

注:如果安装过程里出现了错误,fastlane在输出错误信息的同时,很智能的会在open或者closed的github的issues查找相关的解决方案,输出在console里,供我们fix时做参考。

之所以,fastlane能够智能的提供错误解决方案,得益于fastlane对github上issues的良好管理,当向fastlane的github工程里提issue时候,github的机器上fastlane-bot 会提示你请提供fastlane env在本机上的输出结果。这是个非常非常聪明的作法,利用众包的方式发现问题、解决问题。李飞飞的ImageNet的诞生也离不开众包,进行有监督的学习。

2.2 初始化fastlane

.xcodeproj文件所在的文件夹下执行

fastlane init

会在当前目录下生成一个/fastlane的文件夹。此后所有的fastlane相关文件都会在此文件夹下。

本流程不需要app_identifier、apple_id、team_id的数据,所以不需要Appfile,此处略过。
在生成的文件里,其中最重要的是Fastfile。打开Fastfile文件,定义自动化测试的任务,起名叫test_ui_test,核心代码如下,

lane :test_ui_test do
    ...
    snapshot(
        reinstall_app: true,
        skip_open_summary: true,
        clear_previous_screenshots: false,
        devices: devices
    )
    ...
end

有了入口文件,参考fastlane的各类接口,调用相关actions,如snapshot等内部action,结合普通的ruby语句,我们就可以开始实现自动化截图任务逻辑了。
###2.3 主题测试后自动化需求和方案

1. 需求说明
有钱的iOS2.7.5增加了主题功能,用户可以选择自己喜欢主题,主题数量大概有7种,在制作过程中,发现需要适配iPhone6、iPad、其他iPhone,3种不同的设备。所以我们需要对7 * 3 = 21种情况来检查皮肤是否显示正常(暂时不考虑多语言)。

在测试过程中,除了需要切换不同主题外,还需要切换3种设备;加上主题的配色其实还在迭代中,一旦改动又需要重复的走一遍流程,这个流程对QA而已机械重复而无趣。

所以,我们的目标是如何自动化这部分工作。
2. 思路
多设备适配,可以通过指定snapshot的配置参数devices = ["iPhone 5", "iPhone 6", "iPad Air"]来实现。那如何定义多主题测试呢?总不能默认执行所有皮肤吧——我们需要开始运行时,指定本次测试需要测试哪些皮肤。根据项目情况,决定用主题的服务端配置的id来标示(themeServerId)需要测试哪个皮肤,并且themeServerId有一个对应关系。如下表格

主题serverId 主题名称
001 粉红物语
002 月宫玉兔
003 海底世界
004 缤纷几何
005 宁静夏夜

最终方案是:在执行fastlane 命令的时候去指定主题id,可以指定多个主题,这样就可以实现测试多个主题。但是这个方案遇到一个难题——如何在命令行传参数给app传值。

为什么会采用这种方案?

2.1 有钱的主题的更新机制。
有钱的主题是通过用户在界面选择主题类型,代码里触发

[[ThemeCacheManager sharedManager] downloadThemeWithServerId:serverId]

来实现皮肤的下载。
2.2 fastlane工具链流的限制。

在输入了命令fastlane test_ui_test后,fastlane去调用XCUITest,然后XCUITest启动有钱的APP(重要的一点,XCUITest只是对有钱APP的代理),在XCUITest运行阶段,只有受限的接口去和APP交互;运行前,更不能直接调用ThemeCacheManager的方法。 而且fastlane和XCUITest之间也无法通讯。

2.3 在翻阅了大量fastlane相关资料,、XCUITest相关API资料、启动APP相关资料,发现snapshot的参数里有个宝贝——launch_arguments.

snapshot(
  skip_open_summary: true,
  clear_previous_screenshots: false,
  launch_arguments: [args],
  devices: devices
)

snapshot的configuration file即SnapshotHelper.swift获取到snapshot注入的launch_arguments参数,并且在XCUITest启动的时候设置APP的启动launch_arguments。然而XCUITest却拿不到这个参数,但是通过XCUITest启动的APP,却是可以拿到的——是Session级别的NSUserDefaults数据。使用下列的语句获取传入的themeServerId。

[[NSUserDefaults standardUserDefaults] objectForKey:@"themeServerId"]

在APP的首页拿到这个参数,那么就可以在内部使用这个参数来调用下载皮肤的接口,

[[ThemeCacheManager sharedManager] downloadThemeWithServerId:serverId]

注意:在XCUITest里需要处理下载皮肤的等待。

OK,从fastlane的内置action,snapshot传值给有钱APP的路走通了,如何在fastlane的命令行,传值给snapshot的调用参数呢?比如,我们输入themeServerId变量是终端里,而不是去每次去修改Fastfile里的参数launch_arguments

经过一番搜寻,在fastlane.tools的advanced.md模块找到了答案。fastlane/Advanced.md at master · fastlane/fastlane · GitHub。形如,

fastlane test_ui_test themeServerId:004

就可以传值给snapshot作为调用参数。

2.3 实现test_ui_test任务逻辑(lane)

1 编写Fastfile文件
lane :test_ui_test里编写逻辑——获取themeServerId 、需要测试设备类型,2个参数,然后将获取的参数传入 内置action之snapshot,最后打开截图的summary界面的逻辑。相关代码略过。

2 编写截图的逻辑。
UITests.swift文件里编写截图代码。包括截图内容、截图顺序。

良好配置的测试用例,可以减少测试次数,优化测试方案,节省时间

按照需求,需要截取6个关键界面,但前提是需要先下载到某个主题——解决方案:可以通过触发下载后,等18s来完成。
另外,在正在进入第一个关键页面,即首页前,可能会有新手引导的一系列界面出现,所以需要排除掉新手引导的干扰。

3 对截图的分组展示和区分。
使用默认的截图总览界面,你会发现多次截图都混着一起,浏览的交互方式也很差劲,看起来很不方便,如图;

snapshot默认的截图界面

所以需要一个更好的交互方式、界面组织方式,经过一番思索。对现有的结构做了如下改进

  1. 每次测试后的截图为一组,而且默认一组的展示不需要左右滑动。需要查看所有图片的时候,可以弹窗展示,不需要横向纵向滚动条。
  2. 多次截图,按照先后顺序,向下排列,避免出现左右滚动条。
  3. 当皮肤数量很多时,可以在纵向扩展,通过竖向滚动,即可浏览全部截图。

这就意味着需要自己定制。所以,决定开发一个action实现上述功能。按照官网的步骤,编写resort_screenshot的action,这个后面会详细讲到逻辑实现。这里只需要明白一点:

resort_screenshot对snapshot的截图汇总界面和图片素材做了二次处理

而实现的,并且保留了snapshot原生的汇总界面。所以在lane :test_ui_test里,执行截图命令之后,接着执行如下代码,

resort_snapshot(skip_open_summary: skipOpenSummary)

其中skipOpenSummary表示是否不需要自动打开截图的结果页面。这个选项在测试机上是YES,因为测试是执行完毕之后,会返回一个url,供QA点击在QA本地浏览器打开——并不需要在测试机上打开。

##3. resort_screenshot的action编写

这里需要特殊说明下action、plugin和fastlane的关系。
Action是fastlane里基础的逻辑代码,完成一个特定任务,可以在各个lane的定义里使用,但是只能在内部使用;而plugin则是对action的封装,确定当前action可以依赖的其他plugin的action。功能纯粹,代码良好的plugin可以发布到github之类的地方,供别人在自己的Fastfile里使用。打个比方,

plugin是npm系统里的一个 node_module,而action,可能是node_module里的一个JS文件,是单纯的代码的package

3.1 如何编写action
在命令行输入

fastlane new_action

接着输入action的名字,这里我们输入resort_screenshot,然后会生成
/fastlane/actions/resort_screenshot.rb
打开这个文件,在对应的模板处编写逻辑。

因为resort_screenshot是对snapshot原来的screenshot目录的二次处理,所以,在真正开始编写逻辑之前,查看screenshot目录下的素材结构和展示结果的html(即screenshot.html)的结构。发现这些图都在同一个文件夹,命名是切图序列+自定义文字。另外,还从.gem这个文件夹下,找到了fastlane的snapshot的源码,其中我们最感兴趣的是生成screenshot.html

思考:如何在多次截图的文件夹里,对每一个主题分组呢?这就需要我们知道哪个图片属于哪个主题。换言之,

  1. 第一步,如何把themeServerId传给XCUITest?
  2. 第二部,如何把themeServerId传给我们的自定义action-resort_screenshot?

前面已经说过,从fastlane端传值给XCUITest是不行的;只剩下从APP里向XCUITest传值。

3.2 APP里向XCUITest传值

经过仔细查找,发现XCUITest可以获取到App UIElement里的label,也就是UIControl 的属性accessibilityLabel。 所以解决方案是有钱APP在启动后,使用从NSUserDefaults获取的数据,写到UIControl的accessibilityLabel,然后XCUITest使用

    let xargs = app.staticTexts["fastlane-snapshot-xargs"].label
    let themeServerId: String = xargs.components(separatedBy: "=")[1];

获取到themeServerId,作为截图文件的名字的一部分。

3.3 XCUITest向resort_screenshot传值

正如上述,因为XCUITest获取到themeServerId,作为截图文件的名字的一部分。所以resort_screenshot.rb文件读取到图片文件的名字,经过一次循环之后,获取到了分组的类型和数量等数据,包括themeServerId
resort_screenshot.rb可以获取到themeServerId,传值完成。

注意:XCUITest向resort_screenshot传值,传那些数据是密切和业务相关的,同时在resort_screenshot.erb文件对从XCUITest获取到的数据截取也是业务非常相关的。
后文提供的action的源码其实不是开箱即用的,需要在XCUITest的代码埋数据,在resort_screenshot.rb截取数据,传递给resort_screenshot.erb渲染

至此,整个流程就走通了。整体数据的流向图是,

数据流的走向和实现方式

下面演示如何使用。

如何使用

在流程走通之后,需要移交给QA,让他们可以在测试机上测试。在测试机上的Nginx html目录下新增了一个软连接。

screenshots -> /Users/workspace/src/screenshots

当一个case如命令fastlane test_ui_test themeServerId:002,005 devices:"iPhone 5",测试完毕之后,自动打开浏览器,地址形如

http://youqian.com/screenshot/resort_screenshot.html

作为对照,你还可以打开snapshot默认的结束汇总界面。http://youqian.com/screenshot/screenshot.html

就可以看到测试结果了,这个测试结果地址可以给产品、或者视觉,供他们走查。

关于fastlane test_ui_test的参数

完整的命令如,fastlane test_ui_test themeServerId:002,005 devices:"iPhone 5"。有两个参数,

  1. 一个是themeServerId,接受002, 005这样的value,表示本次需要截图的主题有哪些,必需参数,这些参数对应前文表格数据,是业务相关,不具有通用性。
  2. 第二个是devices,表示在哪些设备上截图。选填,默认是iPhone 5,可以是多个,如”iPhone 5, iPhone 6 PLUS”。但不推荐一次性测试多个设备,因为切换iPhone、iPad模拟器比较耗时。

让我们开始吧。我们的目的是测试themeServerId是002,005的两个皮肤的展示是否正确。执行fastlane test_ui_test themeServerId:002,005 ,经过一段时间编译之后,会自动打开iPhone 5模拟器,自动截图,结束之后,会在控制台输出类似以下的信息,

fastlane执行结束的统计数据

当次任务执行的时间统计,这也是fastlane做工具聚合比较有用的一点——它的输出比较美观。并同时打开浏览器,会看到:

列表模式

每个皮肤的测试作为一个组,以扇形显示。这样不会有横向滚动条。

点击某个扇形里的图片,会打开这张图片,进入大图浏览模式,可以使用左右箭头、或者键盘上的左右按钮来切换图片。

大图浏览模式

至此,大功告成。

其实为了实现这个功能,中间走了很多的弯路,下一节会集中列出来这些坑,或者某些注意事项,让刚刚接触到的人能够避开。

4.注意事项

  1. 如果要使用工具链里的match、cert、sign等需要升级openssl到2.0(登录apple developer portal需求)
  2. 为什么不通过brew cask install fastlane 来安装呢?通过这种命令安装的fastlane,所引用的ruby版本可能是系统默认的版本,如果你有多个ruby安装在系统上,会遇到很多无法预知的情况
  3. sudo gem install fastlane -NV之后不一定成功,运行完毕之后,检查出错在哪儿,最常见的错误是ruby版本不对,如果需要多个ruby版本存在,推荐使用rbenv。
  4. Fastfile的写法很灵活,官网上很很多例子供我们参考,可以拿来吸取灵感。
  5. 细心的读者会发现,其实SnapshotHelper.swift是可以拿到snapshot注入的参数,那么我们的主测试代码里也可以拿到的,那为什么说XCUITest和fastlane不能通讯呢? 是的,其实是可以通讯的,但是snapshot注入的launch_arguments是snapshot自己通过创建了一个中间的snapshot-launch_arguments.txt文件来传递数据,作为一个不属于fastlane体系的类,不应该依赖从黑盒中拿到的逻辑来实现自己的目的——所以我们认为XCUITest和fastlane之间无法直接通讯
  6. 记得经常fastlane snapshot updatelaunch_arguments 在某个版本SnapshotHelper.swift逻辑有问题,我排查了好久才发现是SnapshotHelper.swift的问题,更新到新版本就可以了
  7. 现在维护fastlane的质量还有一些低级的错误。例如刚刚的一个升级提示

CA590D72-3F01-4E34-9107-CA0DC33DFFE3.png

看说明里,是各种修复语法错误,词法错误。没想到,最后的说明里还是有个拼写错误ssigning。真是不应该啊。

结语

对于自动化测试主题这个任务,本身用到fastlane的技术不是很多,也没有很高级的技巧。本文期望作为一个科普类的文章,展示fastlane作为日常工作流程里,解决如何自动化问题的一种尝试。

在各个项目里,用fastlane可以解决什么问题,取决于自己的想象。

文末附上resort_screenshot action的源码,再次强调——并未开箱即用,需要和XCUITest的逻辑配合使用。

附录参考


EOF

Leave a Reply