使用 Cypress 进行可视化回归测试:一种务实的方法

2024-03-08 20:36

本文主要是介绍使用 Cypress 进行可视化回归测试:一种务实的方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

每次组件库 Picasso 发布新版本时,都会更新所有的前端应用程序,让绝大部分新功能能与整个平台的设计保持一致。上个月,推出了 Toptal Talent Portal 的 Picasso 更新,这是我们的用户用来找工作和与客户互动的平台。 已知了这个版本将有设计方面的重要更改,并且为了尽量减少意想不到的问题,使用可视化回归测试技术来帮助我们在发布前发现问题是有意义的。

 视觉回归测试并不是一个新概念。 Toptal 的许多其他项目已经在使用它,包括 Picasso 本身。Percy、Happo 和 Chromatic 等工具可用于帮助团队构建健康的视觉回归流水线,最初确实考虑过添加它们。 最后觉得设置过程太耗时,可能会打乱计划。 我们已经为开始迁移的代码冻结设定了日期,距离截止日期只剩下几天了,但别无选择,只能发挥创意。

通过 UI 测试进行视觉回归测试

虽然我们在项目中没有视觉回归测试,但我们确实很好地使用 Cypress 覆盖了的 UI 集成测试。 尽管这不是该工具的主要用途,但 Cypress 在其文档中有一页专门用于可视化测试,另一页列出了所有可用的插件以帮助配置 Cypress 以进行可视化测试。

从Cypress到屏幕截图

在浏览了可用的文档之后,决定尝试一下 cypress-snapshot-plugin。 设置只需要几分钟,完成以后,我们很快意识到不是在追求传统的视觉回归输出。大多数视觉回归工具通过比较快照和检测已知的、可接受的基线与页面或组件的修改版本之间的像素差异来帮助识别不需要的更改。 如果像素差异大于设定的容差阈值,则页面或组件被标记为需要手动检查。 不过,在此版本中,我们知道我们将对大多数 UI 组件进行一些小的更改,因此设置阈值不适用。 即使给定的组件碰巧有 100% 的不同,它在新版本的上下文中可能仍然是正确的。 同样,小到几个像素的偏差可能意味着组件当前不适合生产。

 那时,两件截然不同的事情变得清晰起来:注意到像素差异无助于识别问题,而对组件进行并排比较正是我们所需要的。 我们将快照插件放在一边,开始使用组件在应用 Picasso 更新之前和之后创建一个图像集合。 这样,就可以快速扫描所有更改,以确定新版本是否仍然符合网站的需求和图书馆的标准。新的计划是截取一个组件的屏幕截图,将其存储在本地,然后在具有更新的 Picasso 版本的分支中截取相同组件的新屏幕截图,然后将它们合并为一个图像。 最终,这种新方法与我们开始的方法并没有太大不同,但它在实施阶段为我们提供了更大的灵活性,因为不再需要导入插件并使用其新命令。

利用 API 比较图像

有了明确的目标,是时候看看 Cypress 如何帮助我们获得所需的屏幕截图了。 如前所述,我们进行了大量的 UI 测试,涵盖了人才门户的大部分内容,因此为了尽可能多地收集关键组件,我们决定在每次交互后截取各个元素的屏幕截图。另一种方法是在测试期间的关键时刻截取整个页面的屏幕截图,但我们认为这些图像太难比较了。 此外,此类比较更容易出现人为错误,例如遗漏页脚已更改的信息。第三种选择是通过每一个测试用例来决定要捕获什么,但这会花费更多时间,因此坚持使用页面上的所有元素似乎是一种实际的妥协。

我们使用Cypress的 API 来生成图像。 cy.screenshot() 命令可以开箱即用地创建单独的组件图像,After Screenshot API 允许重命名文件、更改目录以及区分视觉回归运行和标准回归运行。 通过结合这两者,我们创建了不影响功能测试的运行,并能够将图像存储在适当的文件夹中。首先,我们扩展了插件目录中的 index.js 文件以支持两种新的运行类型(基线和比较)。 然后,根据运行类型设置图像的路径:

 
  1. // plugins/index.js

  2. const fs = require('fs')

  3. const path = require('path')

  4. module.exports = (on, config) => {

  5. // Adding these values to your config object allows you to access them in your tests.

  6. config.env.baseline = process.env.BASELINE || false

  7. config.env.comparison = process.env.COMPARISON || false

  8. on('after:screenshot', details => {

  9. // We only want to modify the behavior of baseline and comparison runs.

  10. if (config.env.baseline || config.env.comparison) {

  11. // We keep track of the file name and number to make sure they are saved in the proper order and in their relevant folders.

  12. // An alternative would have been to look up the folder for the latest image, but this was the simpler approach.

  13. let lastScreenshotFile = ''

  14. let lastScreenshotNumber = 0

  15. // We append the proper suffix number to the image, create the folder, and move the file.

  16. const createDirAndRename = filePath => {

  17. if (lastScreenshotFile === filePath) {

  18. lastScreenshotNumber++

  19. } else {

  20. lastScreenshotNumber = 0

  21. }

  22. lastScreenshotFile = filePath

  23. const newPath = filePath.replace(

  24. '.png',

  25. ` #${lastScreenshotNumber}.png`

  26. )

  27. return new Promise((resolve, reject) => {

  28. fs.mkdir(path.dirname(newPath), { recursive: true }, mkdirErr => {

  29. if (mkdirErr) {

  30. return reject(mkdirErr)

  31. }

  32. fs.rename(details.path, newPath, renameErr => {

  33. if (renameErr) {

  34. return reject(renameErr)

  35. }

  36. resolve({ path: newPath })

  37. })

  38. })

  39. })

  40. }

  41. const screenshotPath = `visualComparison/${config.env.baseline ? 'baseline' : 'comparison'}`

  42. return createDirAndRename(details.path

  43. .replace('cypress/integration', screenshotPath)

  44. .replace('All Specs', screenshotPath)

  45. )

  46. }

  47. })

  48. return config

  49. }

然后通过将相应的环境变量添加到项目的 package.json 中的 Cypress 调用来调用每个运行:

 
  1. "scripts": {

  2. "cypress:baseline": "BASELINE=true yarn cypress:open",

  3. "cypress:comparison": "COMPARISON=true yarn cypress:open"

  4. }

运行新命令后,可以看到运行期间截取的所有屏幕截图都已移动到相应的文件夹中。

接下来,尝试覆盖 cy.get(),这是 Cypress 返回 DOM 元素的主要命令,并对调用的任何元素及其默认实现进行截图。 不幸的是,cy.get() 是一个很难更改的命令,因为在其自己的定义中调用原始命令会导致无限循环。 解决此限制的建议方法是创建一个单独的自定义命令,然后让该新命令在找到元素后截取屏幕截图:

  1. Cypress.Commands.add("getAndScreenshot", (selector, options) => {

  2. // Note: You might need to tweak the command when getting multiple elements.

  3. return cy.get(selector).screenshot()

  4. });

  5. it("get overwrite", () => {

  6. cy.visit("https://example.cypress.io/commands/actions");

  7. cy.getAndScreenshot(".action-email")

  8. })

但是,与页面上的元素进行交互的调用已经包含在内部 getElement() 函数中。 所以我们所要做的就是确保在调用包装器时截取屏幕截图。

通过视觉回归测试得到的结果

一旦我们有了屏幕截图,剩下要做的就是合并它们。 为此,使用 Canvas 创建了一个简单的节点脚本。 最后,脚本能够生成 618 张比较图像! 通过打开人才门户很容易发现一些差异,但有些问题并不那么明显。 

图 4. 不遵循新毕加索指南的示例; 预计会有所不同,但新版本应该有红色背景和白色文本

图 5. 略有损坏的组件布局示例 

为 UI 测试增加价值

首先,添加的视觉回归测试被证明是有用的,并且发现了一些如果没有它们我们可能会错过的问题。 尽管和预计组件会有所不同,但了解实际更改的内容有助于缩小问题案例的范围。 所以,如果你的项目有一个接口,但还没有执行这些测试,那就开始吧!

 这里的第二个教训,也许是更重要的一个教训,是我们再次被提醒完美是好的敌人。 如果我们因为没有事先设置而排除了为此版本运行视觉回归测试的可能性,那么可能会在迁移过程中错过一些错误。 相反,我们商定了一个计划,虽然不理想,但执行起来很快,朝着它努力,就可以得到回报。

总结:

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

 

          视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方进群即可自行领取。

这篇关于使用 Cypress 进行可视化回归测试:一种务实的方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/788325

相关文章

vue使用docxtemplater导出word

《vue使用docxtemplater导出word》docxtemplater是一种邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入任何内容,下面我们来看看如何使用docxtempl... 目录docxtemplatervue使用docxtemplater导出word安装常用语法 封装导出方

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

使用C#代码在PDF文档中添加、删除和替换图片

《使用C#代码在PDF文档中添加、删除和替换图片》在当今数字化文档处理场景中,动态操作PDF文档中的图像已成为企业级应用开发的核心需求之一,本文将介绍如何在.NET平台使用C#代码在PDF文档中添加、... 目录引言用C#添加图片到PDF文档用C#删除PDF文档中的图片用C#替换PDF文档中的图片引言在当

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面