互动生产力进化之路 | 618 淘系前端技术分享
作者: 发布于:

2020年618大促已经过去,作为淘系每年重要的大促活动,淘系前端在其中扮演着什么样的角色,如何保证大促的平稳进行?又在其中应用了哪些新技术?淘系前端团队特此举办了 618 系列征文,为大家介绍 618 中的前端身影。

本篇来自于 淘系互动 前端团队,今年我们带来了名为“幸运列车”的互动游戏,携全国各地的特色农货和美食,让大家在这个夏天寻味中国。从2019年双十一的 “盖楼 ”到今年618的 “开列车”,在大促互动游戏背后,是业务多变性、产品稳定性和研发效率的多重博弈。本文介绍了淘系互动前端团队如何应对研发效率 & 产品稳定性的挑战,内容涵盖“互动智能测试” & “弹窗规模化生产”这两个技术方案。

一. 互动智能测试

当前互动玩法愈发新颖多样,这给业务开发的效率带来很大的挑战,我们需要在视图模型中维护大量的状态。以列车合成区域(下图红框)的状态为例,共有10个合成位可以放置车厢,每个车厢有58个等级,开发的时候需要模拟大量的数据。另外在互动过程中还有抽中红包等概率事件,异常状态情况的验证也有很高的成本。

618互动游戏的玩法可以简单归纳为:用户通过各种行为获取喵币,消耗喵币升级列车换取惊喜红包,最后兑换现金红包的过程。

我们通过机器学习的手段,帮助我们模拟用户的行为,获得真实交互环境中产生的数据,而不是手动枚举方式造测试数据。同时还结合 Puppeteer 模拟真实客户端环境,在线上需要变更时,快速的进行前端功能回归验证,减少研发成本。

强化学习

强化学习是机器学习中的一个领域,强调如何基于环境行动,以取得最大化的预期利益,也意味着能够更快速的到达互动玩法的最终状态。

其中环境通常被规范视为马尔可夫决策过程(MDPs)。首先介绍几个概念:

  • Agent:学习和做决策的主体
  • Environment:Agent 交互的对象
  • State:Agent 的状态
  • Action:行动,Agent 会根据当前的状态选择 Action
  • Reward:奖励

MDPs 简单说就是一个智能体(Agent)采取行动(Action)从而改变自己的状态(State)获得奖励(Reward)与环境(Environment)发生交互的循环过程。Agent 会持续的和环境交互,根据当前的状态选择 Action,而环境会给 Agent 新的状态和 Reward。

为了在项目中生成数据,我们定义了如下 Action:收金币、购买车厢、合成车厢等。Agent 不断与赛车玩法交互,Agent 从初始化状态开始,进行“发送 Action -> 更新状态”的循环,直到最终达到目标“抽大奖”,学习过程到此结束。

学习流程图

我们将学习过程中的状态快照记录了下来,作为服务接口的测试数据,帮助前端侧开发和调试。

ID名称车厢信息剩余金币
State_1初始化数据2 x 车厢等级120000
State_2购买车厢数据3 x 车厢等级115000
................
State_N抽奖数据

自动回归测试

互动场景下的前端交互非常复杂,然而前端功能回归一直以人工方式为主。我们在项目中尝试自动生成测试用例,用于回归测试。

从用户交互的视角出发,收金币、购买车厢、合成车厢都对应用户的一次交互行为。我们在 Puppeteer 环境中运行页面,根据沉淀下来的数据,回溯到特定的状态。并结合强化学习,择优触发前端Action事件,模拟真实的用户操作,最终形成用户的前端交互行为树。

用户交互行为树

得到用户交互行为树之后,我们会对行为路径再进行优化,排除无价值的链路,合并重复链路,并最终拆分成简短的片段便于测试。如合成车厢 N 次,会处理成为 N 个测试用例,尽量以同种状态下的最短路径作为最终的测试用例。

前端测试用例图

在需要回归测试时,我们可以在 Puppeteer 环境中回放测试用例,做到了前端功能的自动回归。这个过程中,我们把各个测试用例的UI快照保存了下来,利用图像识别技术进行最后的校验。

以倒计时浏览任务为例,我们需要验证在跳转后的页面上,是否正确的展示了某个组件,通过图像元素的对比,可以判断该功能点是否正常。

检测到组件,测试用例通过

互动项目的业务逻辑,是一系列用户行为带来的反馈的组合体,智能测试方案在本次 618 互动项目中,成为了前端开发测试阶段的提效利器。在线上阶段发生变更时,可以快速完成线上功能的全量回归和新功能的验证,保障线上业务的稳定。

二. 弹窗规模化生产

今年的618的弹窗场景数量是去年618的2.5倍,弹窗由于可以避免触及到游戏区域的复杂变动,常常被用来满足各类支线乃至主线需求,帮业务完成各个细分领域的玩法覆盖,在无线营销互动领域中,弹窗需求一直处于持续增量的状态。

弹窗看似简单,但每个弹窗都有自己的意义以及属性,可复用范围非常受限,传统方式研发弹窗的成本较高,在业务快速发展的背景下,我们需要有更快更好的研发交付能力。

表达层与逻辑层的解耦

一般情况我们可能会将弹窗沉淀成包含UI的弹窗组件库,也会进一步会将弹窗细节抽象出header、body、button、footer 等配置项。但这样会有一些问题,在互动领域下的一个按钮布局、一个图标形式都会让这个“组件”越来越臃肿,所以不要天真的试图用前端的设计思路,去预判设计师天马行空的设计理念。毕竟不同的玩法和品牌形象下,对UI的定制往往有较强的诉求,因此在营销互动中很难达到真正的UI可复用,因此我们要将表达层完全抽离出来,弹窗方案的逻辑层只负责模型的处理,表达层通过接受数据变化带来的“表达”变化。

例如实现了一个抽奖玩法,逻辑层包含了数据模型、登录初始化请求数据以及抽奖事件的后续逻辑行为,那么该数据模型下最终表达层选用的是老虎机、大转盘还是直接点击抽奖按钮其实都是兼容的。

解耦下的弹窗逻辑层

参照上述的解耦方法,我们将弹窗的能力分为UI层跟逻辑层,大致结构是逻辑通过事件唤起弹窗,先抛开UI层那么先对逻辑进一步结构化,最终逻辑层的结构以及逻辑层跟UI层的关系如下图所示:

逻辑层通过监听业务数据层变换,初始化后Trigger管理器负责从配置队列中检索到匹配条件的行为,开发者几乎可将所有诉求类的弹窗根据Conditions(触发条件)、 Times(展示次数)、Level(层级面)等能力描述出来,并通过配套的runtime快速生成业务所需的逻辑,例如一个初始化进来后的弹窗只需要描述这样一个DSL:

解耦下的弹窗视图层

为了给予设计师更多的发挥空间,我们对UI进一步结构化拆解,直到要达到可以快速编排出UI,以及支持动态下发。

一个项目中往往会有多个弹窗,每个弹窗有许多图层组合,假设类型比较简单只会有组、图片、文案等类型的图层,图层上会有静态&动态的属性描述。

我们可以通过经验所得来的项目 > 场景 > 图层各个维度的拆解,把静态配置+动态绑定等能力对弹窗的UI进行结构化描述,如下图:

首先UI部分是纯函数式的,所以只需要支持UI描述以及动态参数,就可以展示实际的弹窗UI,让业务开发专注在弹窗的逻辑描述上。

配合提供相应的UI设计器,开发者就可以根据需求绘制出所有弹窗,把弹窗的UI开发成本降到最低:


618弹窗部分截图


UI设计器

通过结构化UI层我们就很方便的做很多事情,首先弹窗UI跟逻辑方案都是DSL+Runtime的相同机制,所以只需要配合搭建平台或者异步接口,就能快速支持动态下发的能力。

以及面向开发者协同以及视觉还原的真实预览能力,在发布时,平台上的弹窗预览功能将原来可能需要近半小时的弹窗配置review环节,缩短到几分钟,且准确度更高。