<路径 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

拉斐尔Anachoreta

<路径 clip-rule="evenodd" d="M36.7199 5.27629C35.9382 4.41572 34.9612 3.9729 33.8137 3.9729C32.7077 3.9729 31.6142 4.74992 30.9365 5.51441L30.9407 4.08152L28.8785 4.07734L28.8535 15.8371V15.9917L30.9282 15.9959L30.9365 12.3196C31.5643 13.0256 32.5539 13.4392 33.6557 13.4434C34.8697 13.4434 35.8884 13.0089 36.6866 12.1525C37.4932 11.292 37.9007 10.0763 37.9048 8.68099C37.909 7.25645 37.5099 6.1118 36.7199 5.27629ZM33.3189 5.91128C34.0215 5.91128 34.6161 6.17864 35.0859 6.70084C35.5682 7.20632 35.8177 7.87055 35.8135 8.67682C35.8094 9.48726 35.5724 10.2142 35.0776 10.7405C34.6036 11.246 34.0132 11.505 33.3106 11.505C32.6204 11.505 32.0425 11.2502 31.5436 10.7363C31.0529 10.2058 30.8118 9.47891 30.8118 8.66846C30.816 7.86637 31.0613 7.20214 31.5477 6.69248C32.0383 6.16611 32.6162 5.91128 33.3106 5.91128H33.3189Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M38.9564 5.83606L38.9481 10.799C38.9439 11.8684 39.2391 12.6329 39.8129 13.0674C40.1705 13.3389 40.6278 13.4768 41.1683 13.4768C41.5841 13.4768 42.2368 13.3097 42.2368 13.3097L42.0497 11.5008C42.0497 11.5008 41.5758 11.6554 41.3429 11.4883C41.1226 11.3295 41.0145 10.9953 41.0145 10.4857L41.0228 5.83606L42.9436 5.84024L42.9478 4.11491L41.0269 4.11073L41.0311 1.63345L38.9647 1.62927L38.9606 4.10656L37.2227 4.10238L38.9564 5.83606Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M21.9691 1.49976H11.762L11.7578 3.55093H15.8365L15.8199 13.1426V13.3306L17.9777 13.3347L17.9694 3.55511L21.9691 3.55093V1.49976Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M10.976 6.74679L4.30292 0.0292969L2.74793 1.57917L5.01804 3.86428L0.0371094 8.85227L6.73517 15.5906L8.26521 14.0575L5.97431 11.7515L10.976 6.74679ZM4.5898 10.047C4.54406 10.0596 4.49833 10.0596 4.45259 10.047C4.40686 10.0345 4.36944 10.0136 4.28628 9.93007L3.38406 9.01937C3.30507 8.94 3.28012 8.89822 3.26765 8.85227C3.25517 8.80631 3.25517 8.76036 3.26765 8.71441C3.28012 8.66846 3.30091 8.63086 3.38406 8.54731L6.22793 5.69405C6.30693 5.61467 6.34851 5.58961 6.39424 5.58125C6.43998 5.56872 6.48571 5.56872 6.53145 5.58125C6.57718 5.59378 6.6146 5.61467 6.69775 5.69822L7.59998 6.60893C7.67897 6.6883 7.70392 6.73008 7.71223 6.77603C7.72471 6.82198 7.72471 6.86794 7.71223 6.91389C7.69976 6.95984 7.67897 6.99744 7.59582 7.08099L4.75195 9.93425C4.67295 10.0136 4.63553 10.0303 4.5898 10.047Z" fill="#204ECF" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M26.7591 5.33057C25.915 4.45328 24.7634 3.96869 23.5826 4.00211C22.3893 4.00211 21.3374 4.42404 20.4601 5.25537C19.5704 6.09506 19.1172 7.25642 19.1172 8.71438C19.1172 10.1723 19.5662 11.3337 20.4518 12.1776C21.3332 13.0131 22.3851 13.4392 23.5784 13.4434C23.6241 13.4475 23.6699 13.4475 23.7156 13.4475C24.8299 13.4475 25.9358 12.9671 26.7507 12.1316C27.3453 11.5175 28.0563 10.4313 28.0604 8.73527C28.0646 7.03918 27.3536 5.94885 26.7591 5.33057ZM26.1063 8.73109C26.1021 9.57495 25.8568 10.2726 25.3662 10.8073C24.8798 11.3379 24.2727 11.6052 23.5659 11.6052C22.8716 11.6011 22.2687 11.3337 21.7823 10.799C21.3083 10.2768 21.063 9.57913 21.063 8.72273C21.063 7.86634 21.3083 7.16869 21.7864 6.6465C22.2729 6.11595 22.8758 5.84858 23.5701 5.84858C24.2769 5.84858 24.8839 6.12012 25.3704 6.65485C25.861 7.1854 26.1105 7.88305 26.1063 8.73109Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M45.3026 7.29828C45.3026 6.76773 45.577 5.60637 47.1029 5.60637C47.6434 5.60637 47.951 5.79019 48.2088 6.00742C48.4583 6.2163 48.583 6.73014 48.583 7.15624V7.35677L46.0634 7.75363C44.0677 8.0586 42.9244 9.1364 42.9161 10.7155C42.9119 11.5343 43.2113 12.2361 43.7726 12.75C44.3297 13.2555 45.0864 13.527 45.9845 13.527C47.1153 13.527 47.9635 13.1176 48.5705 12.2737C48.5747 12.633 48.5747 12.9923 48.5747 13.3265H50.6743L50.6868 7.26068C50.691 6.27061 50.3667 5.31395 49.7347 4.78758C49.1069 4.26956 48.2878 4.0022 47.1029 4.0022H47.0862C46.1799 4.0022 45.2194 4.24449 44.575 4.80429C43.8141 5.46434 43.4399 6.5129 43.4399 7.32335L45.3026 7.29828ZM46.2547 11.7098C45.5105 11.7056 45.024 11.2795 45.0282 10.6236C45.0323 9.94267 45.5022 9.5458 46.5125 9.37452L48.583 9.02361V9.0445C48.5789 10.6153 47.6226 11.7098 46.263 11.7098H46.2547Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M51.6846 1.6377L51.668 13.2638V13.3181L53.5265 13.3223L53.5473 1.692V1.64187L51.6846 1.6377Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M55.1973 11.927C55.1973 11.5427 55.3303 11.2085 55.6047 10.9327C55.8791 10.657 56.2076 10.5192 56.5859 10.5192C56.9685 10.5192 57.3052 10.657 57.5755 10.9327C57.8499 11.2085 57.9871 11.5385 57.9871 11.927C57.9871 12.3197 57.8499 12.6455 57.5755 12.9129C57.3011 13.1886 56.9726 13.3223 56.5859 13.3223C56.1993 13.3223 55.8708 13.1886 55.6047 12.9129C55.3345 12.6455 55.1973 12.3197 55.1973 11.927ZM55.4301 11.927C55.4301 12.2528 55.5382 12.5327 55.7669 12.7667C55.9914 12.9923 56.2658 13.1092 56.5943 13.1092C56.9186 13.1092 57.193 12.9923 57.4175 12.7667C57.642 12.5369 57.7584 12.2612 57.7584 11.927C57.7584 11.597 57.642 11.3213 57.4175 11.0957C57.193 10.8659 56.9186 10.7489 56.5943 10.7489C56.27 10.7489 55.9955 10.8659 55.771 11.0957C55.5424 11.3213 55.4301 11.597 55.4301 11.927ZM56.9227 12.6915L56.5485 12.0231H56.2949V12.6915H56.0662V11.1583H56.6857C56.9934 11.1583 57.1763 11.3421 57.1763 11.5928C57.1763 11.8058 57.0183 11.9688 56.8021 12.0105L57.1805 12.6873H56.9227V12.6915ZM56.2949 11.363V11.8309H56.6525C56.852 11.8309 56.9477 11.7557 56.9477 11.6011C56.9477 11.4424 56.8479 11.363 56.6525 11.363H56.2949Z" fill="#262D3D" fill-rule="evenodd">高级QA自动化工程师

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这方面的专家.
现在雇佣

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

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

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

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

Toptal开发者

加入总冠军® 社区.