简介: 从 2019 年双十一的“盖楼”到今年 618 的“开列车”,在大促互动游戏背后,是业务多变性、产品稳定性和研发效率的多重博弈。本文介绍了淘系互动前端团队如何应对研发效率 & 产品稳定性的挑战,内容涵盖“互动智能测试”&“弹窗规模化生产”这两个技术方案。
2020 年 618 大促已经过去,作为淘系每年重要的大促活动,淘系前端在其中扮演着什么样的角色,如何保证大促的平稳进行?又在其中应用了哪些新技术?淘系技术特此推出「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 -> 更新状态”的循环,直到最终达到目标“抽大奖”,学习过程到此结束。
学习流程图
我们将学习过程中的状态快照记录了下来,作为服务接口的测试数据,帮助前端侧开发和调试。
▐ 自动回归测试
互动场景下的前端交互非常复杂,然而前端功能回归一直以人工方式为主。我们在项目中尝试自动生成测试用例,用于回归测试。
从用户交互的视角出发,收金币、购买车厢、合成车厢都对应用户的一次交互行为。我们在 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 环节,缩短到几分钟,且准确度更高。