<路径 clip-rule="evenodd" d="M33.377 4.574a3.508 3.508 0 0 0-2.633-1.126c-1 0-1.993.67-2.604 1.334l.002-1.24-1.867-.002-.02 10.17v.133l1.877.002.008-3.18c.567.611 1.464.97 2.462.973 1.099 0 2.022-.377 2.747-1.117.73-.745 1.1-1.796 1.103-3.002.003-1.232-.358-2.222-1.075-2.945Zm-3.082.55c.637 0 1.176.23 1.602.683.438.438.663 1.012.66 1.707-.003.7-.22 1.33-.668 1.787-.428.438-.964.661-1.601.661-.627 0-1.15-.22-1.6-.666-.445-.46-.662-1.086-.662-1.789.003-.695.227-1.27.668-1.708a2.13 2.13 0 0 1 1.596-.675h.005Zm5.109-.067-.008 4.291c-.002.926.263 1.587.784 1.963.325.235.738.354 1.228.354.376 0 .967-.146.967-.146l-.168-1.564s-.43.133-.64-.01c-.198-.136-.296-.428-.296-.866l.008-4.022 1.738.002.002-1.492-1.738-.002.005-2.144-1.874-.002-.005 2.143-1.573-.002 1.57 1.497ZM20.016 1.305h-9.245l-.002 1.777h3.695l-.016 8.295v.164l1.955.002-.008-8.459 3.621-.002V1.305Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M10.06 5.844 7.277 3.166 4.015.03 2.609 1.374l2.056 1.978-4.51 4.313 6.065 5.831 1.387-1.327-2.073-1.994 4.526-4.331ZM4.274 8.7a.211.211 0 0 1-.124 0c-.04-.013-.074-.03-.15-.102l-.817-.787c-.072-.069-.092-.104-.105-.143a.187.187 0 0 1 0-.12c.013-.039.03-.07.105-.143L5.76 4.938c.072-.07.108-.09.15-.099a.21.21 0 0 1 .123 0c.041.012.075.03.15.101L7 5.727c.072.07.093.104.103.144.013.04.013.08 0 .119-.013.04-.03.072-.106.143L4.422 8.601a.325.325 0 0 1-.147.099Z" fill="#204ECF" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M24.354 4.622a3.94 3.94 0 0 0-2.876-1.149 4.1 4.1 0 0 0-2.829 1.084c-.804.725-1.214 1.733-1.217 2.992-.002 1.26.405 2.267 1.207 2.995a4.114 4.114 0 0 0 2.832 1.094c.04.002.082.002.123.002a3.967 3.967 0 0 0 2.75-1.138c.538-.532 1.183-1.473 1.186-2.938.002-1.465-.637-2.408-1.176-2.942Zm-.59 2.94c-.003.73-.228 1.334-.671 1.794-.441.458-.99.69-1.633.69a2.166 2.166 0 0 1-1.614-.697c-.43-.45-.65-1.057-.65-1.797s.222-1.344.655-1.795a2.17 2.17 0 0 1 1.617-.69c.64 0 1.189.235 1.63.698.443.46.668 1.064.665 1.797ZM41.15 6.324c0-.458.25-1.465 1.632-1.465.49 0 .768.159 1.003.347.227.18.34.626.34.994v.174l-2.282.341C40.035 6.98 39 7.913 38.993 9.28c-.002.708.266 1.314.777 1.76.503.438 1.191.67 2.004.673 1.023 0 1.792-.354 2.341-1.084.003.31.003.621.003.91h1.903l.013-5.246c.002-.856-.289-1.685-.864-2.14-.567-.449-1.31-.679-2.386-.681h-.015c-.82 0-1.69.208-2.274.695-.689.572-1.027 1.478-1.027 2.178l1.682-.02Zm.864 3.814c-.676-.002-1.115-.371-1.112-.938.003-.589.43-.933 1.346-1.081l1.875-.305v.017c-.005 1.36-.87 2.307-2.102 2.307h-.008Zm4.917-8.712-.018 10.058v.044l1.684.005.018-10.06v-.045l-1.684-.002Zm2.654 9.491c0-.173.062-.322.19-.445a.645.645 0 0 1 .462-.186c.18 0 .338.062.465.186a.596.596 0 0 1 .193.445.583.583 0 0 1-.193.443.644.644 0 0 1-.465.183.634.634 0 0 1-.461-.183.59.59 0 0 1-.191-.443Zm.108 0c0 .146.052.273.158.376a.54.54 0 0 0 .389.154.539.539 0 0 0 .547-.53.498.498 0 0 0-.16-.373.531.531 0 0 0-.387-.156.531.531 0 0 0-.387.155.497.497 0 0 0-.16.374Zm.702.344-.176-.3h-.118v.3h-.109v-.688h.292c.144 0 .23.082.23.196 0 .096-.076.168-.176.188l.178.304h-.121Zm-.294-.596v.21h.167c.093 0 .14-.034.14-.104 0-.072-.047-.106-.14-.106h-.167Z" fill="#262D3D" fill-rule="evenodd">作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
拉斐尔·阿纳霍雷塔的头像

拉斐尔Anachoreta

Rafael是一名高级QA工程师,拥有近10年的测试工作经验, 自动化, 通过监督,让团队和产品变得更好.

每次都有一个新版本的组件库, 毕加索, 被释放, 我们更新了所有前端应用程序,以充分利用新功能,并在网站的所有部分调整我们的设计.

上个月, 我们推出了一个毕加索更新到Toptal人才门户, 我们的人才用来找工作和与客户互动的平台. 知道新版本的发布会带来重大的设计变化, 为了尽量减少意想不到的问题, 使用视觉回归测试技术帮助我们在发布之前发现问题是有意义的.

视觉回归测试并不是一个新概念; plenty of other projects at Toptal already use it, 包括毕加索本人.

像Percy这样的工具, Happo, 和Chromatic可以用来帮助团队构建健康的视觉回归管道, 我们一开始确实考虑过添加它们. 我们最终决定设置过程太耗时,可能会打乱我们的计划. 我们已经为开始迁移的代码冻结设定了日期, 离最后期限只有几天了, 我们别无选择,只能发挥创造力.

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

虽然我们在项目中没有视觉回归测试, 我们使用柏树进行了很好的UI集成测试. 尽管这并不是这个工具的主要用途, 赛普拉斯在其文档中有一页专门用于 视觉检测 另一个列出了所有的 可用插件 来帮助配置柏树进行视觉测试.

从赛普拉斯到截图

在浏览了可用的文档之后,我们决定尝试一下柏树-snapshot-plugin. 只花了几分钟就设置好了, 一旦我们做到了, 我们很快意识到我们追求的不是传统的视觉回归输出.

大多数可视化回归工具通过比较快照和检测已知元素之间的像素差异来帮助识别不需要的更改, 可接受的基线和页面或组件的修改版本. 如果像素差大于设置的公差阈值, 将页面或组件标记为手动检查. 在本新闻稿中, 虽然, 我们知道我们将对大多数UI组件进行一些小的更改, 所以设置一个阈值是不适用的. 即使给定的组件碰巧是100%不同的, 它在新版本的上下文中可能仍然是正确的. 类似的, 一个小到几个像素的偏差可能意味着一个组件目前不适合生产.

描述测试运行的预期结果和实际结果的屏幕截图.
图1. 小像素差异导致假阴性的例子

在这一点上, 两件截然不同的事情变得清晰起来:注意像素差异并不能帮助识别问题, 而对这些组件进行并排比较正是我们所需要的. 我们将快照插件放在一边,并开始使用我们的组件在应用毕加索更新之前和之后创建图像集合. 这种方式, 我们可以快速浏览所有的更改,以确定新版本是否仍然符合网站的需求和图书馆的标准.

新的计划是截取一个组件的屏幕截图, 本地存储, 用更新后的毕加索版本在分支中截取相同组件的新截图, 然后将它们合并成一张图像. 最终, 这种新方法与我们开始的方法没有太大不同, 但是它在实现阶段为我们提供了更大的灵活性,因为我们不再需要导入插件并使用它的新命令.

显示可视化比较流程的图表, 在可视化测试运行后,新旧版本的映像是如何合并的.
图2. 视觉比较流程

利用api进行图像比较

心中有一个明确的目标, 是时候看看柏树如何帮助我们获得所需的截图了. 正如前面提到的, 我们有大量的UI测试覆盖了大部分的人才门户, 所以为了收集尽可能多的关键部件, 我们决定在每次交互后截取单个元素的屏幕截图.

另一种方法是在测试过程中的关键时刻截取整个页面的屏幕截图, 但我们认为这些图像很难进行比较. 此外,这种比较可能更容易出现人为错误,例如遗漏了页脚已更改.

第三种选择是遍历每个单独的测试用例来决定捕获什么, 但那样会花费更多的时间, 因此,坚持页面上使用的所有元素似乎是一种实际的妥协.

我们转向柏树的API来生成图像. 的 cy.截图() command 可以开箱即用地创建组件的单独映像,以及 截图后的API 允许我们重命名文件, 更改目录, 并区分视觉回归运行和标准运行. 将两者结合起来, 我们创建的运行不会影响我们的功能测试,并使我们能够将映像存储在相应的文件夹中.

首先,我们扩展了 指数.js 文件,以支持两种新的运行类型(基线和比较). 然后,我们根据运行类型为图像设置路径:

/ /插件/索引.js
Const fs = require('fs')
Const 路径 = require('路径')
模块.exports = (on, 配置) => {
//将这些值添加到您的配置对象中,您可以在测试中访问它们.
  配置.env.基线=过程.env.BASELINE || false
  配置.env.比较=过程.env.比较|| false

  on('after:screenshot', details => {
    //我们只想修改基线和比较运行的行为.
    如果配置.env.基线||配置.env.比较){
      //我们会记录文件名和编号,以确保它们按正确的顺序保存在相应的文件夹中.
      //另一种方法是在文件夹中查找最新的映像, 但这是更简单的方法.
      let lastScreenshotFile = "
      让lastScreenshotNumber = 0

      //我们给图像添加适当的后缀号,创建文件夹,并移动文件.
      const createDirAndRename = filePath => {
        if (lastScreenshotFile === filePath) {
          lastScreenshotNumber + +
        } else {
          lastScreenshotNumber = 0
        }
        lastScreenshotFile = filePath
        const newPath = filePath.替换(
          '.png”,
          " # $ {lastScreenshotNumber}.png”
        )

        return new Promise((resolve, reject) => {
          fs.mkdir(路径.dirname(newPath), { recursive: true }, mkdirErr => {
            if (mkdirErr) {
              返回拒绝(mkdirErr)
            }
            fs.重命名(细节.路径, newPath, renameErr => {
              if (renameErr) {
                返回拒绝(renameErr)
              }
              解析({路径: newPath})
            })
          })
        })
      }

      const screenshotPath = ' visualComparison/${配置.env.基线 ? '基线': 'comparison'} '

      返回createDirAndRename(细节.路径
        .替换(柏树/集成,screenshotPath)
        .替换('All Specs', screenshotPath)
      )
    }
  })
  返回配置
}

然后,我们通过向项目中的柏树调用添加相应的环境变量来调用每次运行 包.json:

"脚本":{
  "柏树:基线": " 基线 =真纱柏树:open",
  "柏树:比较":"比较=真纱柏树:开放"
}

一旦我们运行了新的命令, 我们可以看到,在运行期间拍摄的所有屏幕截图都被移动到适当的文件夹中.

显示运行期间拍摄的图像并移动到文件夹的屏幕截图.
图3. 可视化运行结果

接下来,我们尝试覆盖 cy.get ()柏树的 main命令返回DOM元素,并截取所有被调用的元素及其默认实现的屏幕截图. 不幸的是, cy.get () 更改命令是否很棘手,因为在其自己的定义中调用原始命令 导致无限循环. 解决此限制的建议方法是创建一个单独的自定义命令,然后让新命令在找到元素后截图:

柏树.命令.add("getAndScreenshot", (selector, options) => {
  //注意:当获取多个元素时,可能需要调整命令.
  返回cy.(选择).截图()
});

it("get overwrite", () => {
  cy.访问(“http://example。.柏树.io /命令/行动”);
  cy.getAndScreenshot(“.action-email”)
})

然而,我们与页面上的元素交互的调用已经被封装在一个内部的 getElement () 函数. 所以我们所要做的就是确保在调用包装器时截取截图.

结果通过视觉回归检验获得

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

不正确使用毕加索的前后例子,在元素中显示红色和黑色.
图4. Example of not following new 毕加索 guidelines; a difference 预期,但新版本应该有一个红色的背景和白色的文字

一个稍微破碎的组件布局前后的例子, 在“After”图像的复选框旁边显示不对齐的文本.
图5. 一个稍微破碎的组件布局的例子

为UI测试增加价值

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

这里的第二个教训,也许是更重要的一个教训,是我们再次被提醒 完美是善的敌人. 如果我们已经排除了为这个版本运行视觉回归测试的可能性,因为没有预先设置, 我们可能在迁移过程中遗漏了一些bug. 而不是, 我们同意了一个计划, 虽然不理想, 执行速度快, 我们朝着这个方向努力, 它得到了回报.

有关在项目中实现健壮的可视化回归管道的详细信息, 请参考 赛普拉斯的视觉测试页面,选择最适合您需求的工具,并观看教程视频.

了解基本知识

  • 什么是视觉回归测试/视觉UI测试?

    视觉回归测试是一种回归测试,它可以确保已知正确的网页或组件没有意外地发生变化.

  • 视觉回归测试是如何工作的?

    可视化回归测试的工作原理是将应用程序的可视化方面与已知基线进行比较. 通常, 这是通过区分图像中的像素来实现的, 尽管一些解决方案使用人工智能来区分真实差异和误报.

  • 回归测试是什么类型的测试?

    回归测试是对先前测试过的应用程序进行更改后的测试,以确保没有由于所述更改而引入缺陷.

  • 我们为什么要做回归测试?

    我们运行回归测试是为了防止在进行更改时在应用程序中引入意外或不需要的行为.

就这一主题咨询作者或专家.
预约电话

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.