使用 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 测试增加价值

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

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

总结:

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

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

软件测试面试文档

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

 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/440720.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++指针(四)万字图文详解!

个人主页:PingdiGuo_guo 收录专栏:C干货专栏 前言 相关文章:C指针(一)、C指针(二)、C指针(三) 本篇博客是介绍函数指针、函数指针数组、回调函数、指针函数的。 点赞破六…

结构体和malloc学习笔记

结构体学习: 为什么会出现结构体: 为了表示一些复杂的数据,而普通的基本类型变量无法满足要求; 定义: 结构体是用户根据实际需要自己定义的符合数类型; 如何使用结构体: //定义结构体 struc…

【工具】Raycast – Mac提效工具

引入 以前看到同事们锁屏的时候,不知按了什么键,直接调出这个框,然后输入lock屏幕就锁了。 跟我习惯的按Mac开机键不大一样。个人觉得还是蛮炫酷的~ 调研 但是由于之前比较繁忙,这件事其实都忘的差不多了&#xff0…

C++ · 代码笔记4 ·继承与派生

目录 前言010继承与派生简单例程020多级继承030使用using关键词更改访问权限040隐藏050派生类与基类成员函数同名时不构成重载060使用多级继承展示成员变量在内存中的分布情况071派生类在函数头调用基类构造函数072构造函数调用顺序080构造函数与析构函数的调用顺序091多重继承…

【常见集合】Java 常见集合重点解析

Java 常见集合重点解析 1. 什么是算法时间复杂度? 时间复杂度表示了算法的 执行时间 和 数据规模 之间的增长关系; 什么是算法的空间复杂度? 表示了算法占用的额外 存储空间 与 数据规模 之间的增长关系; 常见的复杂度&#x…

超实用的公众号搭建教程分享,纯干货

微信公众号已经成为了企业、个人和品牌进行宣传和互动的重要平台。在这个拥有海量公众号的时代,如何让你的公众号脱颖而出,吸引更多的关注者,实现有效传播呢?接下来,伯乐网络传媒将为你详细解析公众号搭建教程&#xf…

便捷在线导入:完整Axure元件库集合,让你的设计更高效!

Axure元件库包含基本的工具组件,可以使原型绘制节省大量的重复工作,保持整个设计页面的一致性和标准化,同时显得专业。Axure元件库就像我们日常生活中的门把手、自行车踏板和桌子上的螺丝钉,需要组装才能使用。作为一名成熟的产品…

信息安全管理与评估DCST-6000B-Pro神州数码堡垒机沙箱连接教程

信息安全管理与评估DCST-6000B-Pro神州数码堡垒机沙箱连接教程 一、前言 在全国职业院校技能大赛-信息安全管理与评估赛项中,我们会用到DCST-6000B-Pro神州数码堡垒机沙箱,简称堡垒机, 很多院校并没有购买该设备,导致备赛学生可…

阿珊详解Vue Router的守卫机制

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

安全防御第七次实验

需求:在FW7和FW8之间建立一条IPSEC通道保证10.0.2.0/24网段可以正常访问到192.168.1.0/24 一、NAT配置 FW4: FW6: 二、在FW4上做服务器映射 三、配置IPSEC FW5: FW6: 四、防火墙上的安全策略 FW4: FW5:…

spring cloud 之 Netflix Eureka

1、Eureka 简介 Eureka是Spring Cloud Netflix 微服务套件中的一个服务发现组件,本质上是一个基于REST的服务,主要用于AWS云来定位服务以实现中间层服务的负载均衡和故障转移,它的设计理念就是“注册中心”。 你可以认为它是一个存储服务地址信息的大本…

Androidstudio实现登录按钮按下变色

在activity_main.xml中&#xff0c;写如下代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"androi…

禁止使用搜索引擎,你了解吗?

员工A&#xff1a;“我今天想搜索的时候&#xff0c;用不了浏览器了&#xff0c;你能用么&#xff1f;” 员工B&#xff1a;“不知道啊我试一下啊” “也不行” 员工C&#xff1a;“为什么啊&#xff1f;” 针对上述对话&#xff0c;我们不禁思考&#xff1a; 公司为什么禁…

2023最新群智能优化算法:巨型犰狳优化算法(Giant Armadillo Optimization,GAO)求解23个基准函数(提供MATLAB代码)

一、巨型犰狳优化算法 巨型犰狳优化算法&#xff08;Giant Armadillo Optimization&#xff0c;GAO&#xff09;由Omar Alsayyed等人于2023年提出&#xff0c;该算法模仿了巨型犰狳在野外的自然行为。GAO设计的基本灵感来自巨型犰狳向猎物位置移动和挖掘白蚁丘的狩猎策略。GAO…

Spring Boot中SQL语句报错

报错原因&#xff1a; You have an error in your SQL syntax 你的SQL语句出现错误 报错位置&#xff1a; check the manual that corresponds to your MySQL server version for the right syntax to use near :/sql/schema.sql.t_film at line 1 在:/sql/schema.sql附近使用…

前端使用Ant Design中的Modal框实现长按顶部拖动弹框需求

需求&#xff1a;需要Ant Design中的一个Modal弹框&#xff0c;并且可以让用户按住顶部实现拖动操作 实现&#xff1a;在Ant Design的Modal框的基础上&#xff0c;在title中添加一个onMouseDown去记录拖拽的坐标&#xff0c;然后将其赋值给Modal的style属性 代码部分&#xff…

20 卷积层里的填充和步幅【李沐动手学深度学习v2课程笔记】

1. 填充和步幅 在上下左右分别填充一些0 2. 代码实现 2.1 填充 我们创建一个高度和宽度为3的二维卷积层&#xff0c;并在所有侧边填充1个像素。给定高度和宽度为8的输入&#xff0c;则输出的高度和宽度也是8。 import torch from torch import nn# 为了方便起见&#xff0c;…

smplx pkl格式可视化

smplx pkl格式可视化 import glob import os import pickleimport torch import numpy as npfrom smplpytorch.pytorch.smpl_layer import SMPL_Layer from display_utils import display_model, display_model_continuousfrom matplotlib import pyplot as plt from matplotl…

详解float函数类型转换

函数描述 float([x]) 函数将数字或数字的字符串表示形式转换为与它等效的有符号浮点数。如果参数x是一个字符串&#xff08;十进制表示的数字串&#xff09;&#xff0c;数字前面可以添加符号来表示正数&#xff0c;或负数。符号和数字之间不能出现空格&#xff0c;但是符号前…

Spring学习 基础(二)Bean和AOP

3、Spring Bean Bean 代指的就是那些被 IoC 容器所管理的对象&#xff0c;我们需要告诉 IoC 容器帮助我们管理哪些对象&#xff0c;这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。 Bean的创建方式 1. XML 配置文件&#xff1a; 传统上&am…