Skip to main content
Cypress应用

与元素交互

info
你将学习到
  • Cypress如何判断元素是否可操作
  • 如何调试不可操作元素
  • 如何忽略Cypress的可操作性检查
tip

了解UI覆盖率如何高亮显示未测试的交互元素区域。掌握覆盖率缺口,优化测试策略,自信交付高质量代码。

可操作性

Cypress中的某些命令用于与DOM交互,例如:

我们称这些为"操作命令"。这些操作模拟用户与应用交互。底层实现上,Cypress会触发浏览器事件,从而激活应用的事件绑定。

在执行任何命令前,我们会检查DOM的当前状态,并采取一些措施确保DOM元素"准备就绪"接收操作。

Cypress会持续监控DOM——重新运行产生当前主题的查询——直到元素在defaultCommandTimeout持续时间内通过所有检查(详见隐式断言核心概念指南)。

执行的检查和操作

当Cypress无法与元素交互时,可能在上述任何步骤失败。通常会收到错误信息说明元素为何不可操作。

可见性

Cypress通过多项计算判断元素可见性,考虑CSS变换和变形效果。

以下情况元素被视为隐藏:

  • 宽度或高度为0
  • 自身或祖先CSS属性为visibility: hidden
  • 自身或祖先CSS属性为display: none
  • CSS属性为position: fixed且位于屏幕外或被覆盖
  • 任何祖先元素隐藏溢出*
    • 且该祖先宽度或高度为0
    • 且该祖先与元素之间存在position: absolute元素
  • 任何祖先元素隐藏溢出*
    • 且该祖先或中间祖先是其偏移父级
    • 且元素位于该祖先边界外
  • 任何祖先元素隐藏溢出*
    • 且元素为position: relative
    • 且位于该祖先边界外

*隐藏溢出overflow: hiddenoverflow-x: hiddenoverflow-y: hiddenoverflow: scrolloverflow: auto

info
透明度

直接断言元素可见性时,自身或祖先CSS属性为opacity: 0的元素被视为隐藏。

但CSS属性(或祖先)为opacity: 0的元素仍被视为可操作,任何与隐藏元素交互的命令都会执行操作。

禁用状态

Cypress检查表单控件元素(如buttoninput)的disabled属性是否为true。在其他元素上设置disabled属性不会影响用户交互能力,也不影响Cypress的可操作性检查。

脱离文档

Cypress检查断言元素是否仍在测试应用的document中。

当应用重新渲染DOM时,通常会移除DOM元素并在原位置插入带有新属性的新DOM元素。这就是为什么不应将_操作命令_链式调用很重要——Cypress可以重新查询定位新元素,但不会重新运行命令

只读状态

.type()操作期间,Cypress检查元素的readonly属性是否设置。

动画效果

Cypress会自动检测元素是否在动画中并等待其停止。

通过采样元素最近位置计算斜率来判断是否在动画(还记得八年级代数吗?😉)。

我们检查元素自身当前位置与先前位置。如果距离超过animationDistanceThreshold,则认为元素在动画中。

这个阈值是通过实验找到的用户交互感觉"过快"的速度。你可以随时调整此阈值

也可以通过配置选项waitForAnimations关闭动画检查。

覆盖状态

我们确保尝试交互的元素未被父元素覆盖。

例如,元素可能通过所有先前检查,但全屏弹窗可能使其无法被真实用户交互。

info

检查元素是否被覆盖时,我们总是检查其中点坐标。

如果元素的_子元素_覆盖它——这是允许的。实际上我们会自动将事件触发到该子元素。

假设有个按钮:

<button>
<i class="fa fa-check">
<span>Submit</span>
</button>

通常<i><span>元素会覆盖我们尝试交互的精确坐标。这种情况下,事件会在子元素上触发。命令日志中会记录这一点。

滚动

与元素交互前,我们总是将其滚动到视图中(包括其父容器)。即使元素原本可见,我们仍执行滚动算法以确保每次命令运行行为一致。

info

此滚动逻辑仅适用于上述可操作命令。使用cy.get().find()等DOM命令时不会滚动元素到视图。

默认情况下,滚动算法将命令作用元素的左上角点滚动到其可滚动容器的左上角可滚动点。

滚动后如果元素仍被覆盖,我们会继续滚动并"微调"页面直到其可见。这种情况常见于position: fixedposition: sticky导航元素固定在页面顶部时。

我们的算法_应该_总能通过滚动使元素不被覆盖。

要改变滚动时元素在视口中的位置,可使用scrollBehavior配置选项。当元素对齐视口顶部被覆盖时很有用,或仅偏好操作命令时元素居中。可选值:'center''top''bottom''nearest'falsefalse完全禁用滚动)。

坐标

确认元素可操作后,Cypress会触发所有适当事件和对应默认操作。通常这些事件的坐标在元素中心触发,但多数命令允许改变触发位置。

cy.get('button').click({ position: 'topLeft' })

触发事件的坐标通常可在命令日志中点击命令查看。

事件坐标

此外会显示红色"命中框"——表示事件坐标的点。

命中框

调试

当Cypress认为元素不可操作时,调试可能比较困难。

虽然应该能看到清晰的错误信息,但没有什么比自己直观检查和操作DOM更能理解原因。

命令日志中悬停命令时,我们会将命令作用的元素滚动到视图。请注意这与上述算法_不同_。

实际上我们只在运行可操作命令时使用上述算法滚动元素。常规DOM查询如cy.get().find()_不会_滚动元素到视图。

悬停快照时滚动元素到视图是为了帮助你查看命令找到的元素。这是纯视觉功能,不一定反映命令运行时页面的实际状态。

换句话说,无法通过先前快照准确看到Cypress"看到"的内容。

唯一"看到"并调试Cypress认为元素不可见原因的方法是使用debugger语句。

建议在操作命令_前_直接放置debugger或使用.debug()命令。

确保开发者工具打开,你可以非常接近"看到"Cypress执行的计算。

还可以绑定事件来深入了解Cypress如何处理元素。结合调试器使用这些事件可获得更低层次的视角。

// 在操作命令前设置调试断点
cy.get('button').debug().click()

强制操作

虽然上述检查非常有助于发现阻止用户交互的情况——但有时它们会碍事!

有时不值得让机器人完全模仿用户操作元素的精确步骤。

想象有个嵌套导航结构,用户必须精确悬停和移动鼠标才能到达目标链接。

测试时值得复制这种操作吗?

也许不!对于这些场景,我们提供绕过所有检查强制触发事件的方案!

大多数操作命令可传入{ force: true }

// 强制点击并触发所有后续事件
// 即使元素不被认为是'可操作的'
cy.get('button').click({ force: true })
info
有什么区别?

强制触发事件时我们会:

  • 继续执行所有默认操作
  • 强制在元素上触发事件

我们不会执行:

  • 滚动元素到视图
  • 确保可见
  • 确保未禁用
  • 确保未脱离文档
  • 确保非只读
  • 确保无动画
  • 确保未被覆盖
  • 在子元素上触发事件

总结:{ force: true }跳过检查,始终在目标元素上触发事件。

caution

强制选择禁用选项

.select()传递{ force: true }不会覆盖选择禁用<option>或禁用<optgroup>内选项的可操作性检查。详见此issue