Cypress打开新标签页无法切换 spy监听window.open失效报错
在Cypress自动化测试过程中遇到如下场景:点击指定链接元素会触发新标签页/新窗口打开逻辑,因Cypress原生不支持多标签页测试能力,尝试使用监听window.open的变通方案实现测试逻辑,但方案未生效:点击元素后浏览器确实打开了新标签页,但测试逻辑无法切换到新标签页上下文,用例执行失败抛出错误:
expected redirect to have been called at least once, but it was never called.
原有测试代码如下:
cy.visit('https://qa.abc.com/xyz/documents?action_id=1'); cy.window().then((win) => { cy.spy(win, 'open').as('redirect'); }); cy.get(':nth-child(1) > [style="width: 228px;"] > .text-ellipsis') .click(); cy.get('@redirect') .should('be.called');
补充说明:该场景下跳转的目标URL为动态生成,与JavaScript逻辑绑定,既无法直接从控制台获取到目标URL,也无法通过移除链接属性的方式修改跳转逻辑。
问题报错截图
以下HTML代码可完整复现该问题:
<html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title>Change Content - Javascript</title> <meta name="robots" content="noindex, nofollow"> <meta name="googlebot" content="noindex, nofollow"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> </head> <body> <a id="myelement" href="javascript:void(0);" onclick="openNewTab()" "">Open New Tab </a> <script type="text/javascript">//<![CDATA[ function openNewTab() { window.openNew({ target:'test_blank', url: 'https://www.google.co.in/' }); } function openNew(option) { const form = document.createElement("form"); form.target = option.target || "_blank"; form.action = option.url; form.method = "GET"; document.body.appendChild(form); form.submit(); $(form).remove(); } //]]> </script> </body> </html>
原有window.open监听方案失效的原因非常直接:当前页面的新标签页跳转逻辑,根本没有调用原生window.openAPI。
从可复现代码可以看到,跳转逻辑是自定义实现的:点击元素后触发自定义的openNewTab方法,内部调用window.openNew函数,动态创建一个带target属性的form表单,将目标地址设为表单的action属性后调用form.submit()触发提交,完全靠form的target属性打开新标签页,全程没有调用window.open,给window.open挂载的spy自然捕获不到任何调用,最终抛出上述错误。
Cypress原生不支持多标签页上下文切换,针对这种动态form提交触发新标签的场景,不需要切换标签上下文,直接拦截表单提交行为即可完成校验,以下是两种可直接落地的方案:
方案1:拦截form提交原型方法,捕获跳转参数做断言
通过stub掉表单原生submit方法的方式埋点,不管页面什么时候动态创建form,只要触发submit就会被拦截,不需要提前绑定DOM事件,代码如下:
cy.visit('https://qa.abc.com/xyz/documents?action_id=1'); cy.window().then((win) => { // 拦截所有form的submit原生方法 cy.stub(win.HTMLFormElement.prototype, 'submit').callsFake(function() { // this指向当前触发提交的form实例,直接读取跳转参数 const jumpData = { target: this.target, url: this.action } // 将参数挂到window对象上方便后续断言 win.__test_jump_data = jumpData; }).as('formSubmitSpy'); }); // 点击触发跳转的元素 cy.get(':nth-child(1) > [style="width: 228px;"] > .text-ellipsis') .click(); // 断言跳转逻辑触发 cy.get('@formSubmitSpy').should('be.called'); // 校验跳转参数符合预期,动态URL可通过包含关键字的方式校验 cy.window().its('__test_jump_data').should((data) => { expect(data.target).to.equal('test_blank'); expect(data.url).to.include('目标地址的关键路径/参数片段'); });
方案2:修改跳转目标为当前窗口,直接校验跳转后页面内容
如果需要校验跳转后页面的内容,可以在拦截submit的时候将form的target改为_self,让表单提交在当前Cypress控制的窗口内完成,不需要处理多标签上下文,代码如下:
cy.visit('https://qa.abc.com/xyz/documents?action_id=1'); cy.window().then((win) => { const originSubmit = win.HTMLFormElement.prototype.submit; cy.stub(win.HTMLFormElement.prototype, 'submit').callsFake(function() { // 将跳转目标改成本窗口,避免打开新标签 this.target = '_self'; // 调用原生submit逻辑完成跳转 originSubmit.call(this); }); }); // 点击元素,等待当前页面跳转到目标地址 cy.get(':nth-child(1) > [style="width: 228px;"] > .text-ellipsis') .click(); // 后续直接按照单页面跳转的逻辑,校验URL、页面元素即可 cy.url().should('include', '目标地址的关键路径');
- 不要盲目套用
window.open监听的多标签测试方案:该方案仅对业务代码显式调用window.open()打开新页的场景有效,带target属性的a标签跳转、动态form提交触发的新标签都不会调用window.open,监听必然失效。 - 动态生成的DOM元素不要等渲染后再绑定事件:本场景下的form是点击后临时创建、提交后立刻删除的,必须通过拦截原型方法、document层事件委托的方式提前埋点,否则会错过事件触发时机。
- 动态跳转地址不需要提前从控制台抓取:拦截到form提交事件后,直接读取form实例的
action属性就能拿到完整的最终跳转地址,支持做路径匹配、参数校验。
内容的提问来源于stack exchange,提问作者Tausif Shaikh

